1
0
Fork 0

Codechange: Add the universal selector widget.

pull/12793/head
Andrii Dokhniak 2024-06-30 16:52:50 +02:00
parent 6006e832f2
commit 2af1065c4e
6 changed files with 505 additions and 7 deletions

View File

@ -378,6 +378,8 @@ add_files(
screenshot_gui.h screenshot_gui.h
screenshot.cpp screenshot.cpp
screenshot.h screenshot.h
selector_gui.cpp
selector_gui.h
settings.cpp settings.cpp
settings_cmd.h settings_cmd.h
settings_func.h settings_func.h

View File

@ -619,18 +619,18 @@ STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION :{WHITE}Cargo Pa
STR_GRAPH_CARGO_PAYMENT_RATES_DAYS :{TINY_FONT}{BLACK}Days in transit STR_GRAPH_CARGO_PAYMENT_RATES_DAYS :{TINY_FONT}{BLACK}Days in transit
STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS :{TINY_FONT}{BLACK}Seconds in transit STR_GRAPH_CARGO_PAYMENT_RATES_SECONDS :{TINY_FONT}{BLACK}Seconds in transit
STR_GRAPH_CARGO_PAYMENT_RATES_TITLE :{TINY_FONT}{BLACK}Payment for delivering 10 units (or 10,000 litres) of cargo a distance of 20 squares STR_GRAPH_CARGO_PAYMENT_RATES_TITLE :{TINY_FONT}{BLACK}Payment for delivering 10 units (or 10,000 litres) of cargo a distance of 20 squares
STR_GRAPH_CARGO_ENABLE_ALL :{TINY_FONT}{BLACK}Enable all
STR_GRAPH_CARGO_DISABLE_ALL :{TINY_FONT}{BLACK}Disable all # Selector widget
STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL :{BLACK}Display all cargoes on the cargo payment rates graph STR_SELECTOR_WIDGET_ALL :{BLACK}All
STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Display no cargoes on the cargo payment rates graph STR_SELECTOR_WIDGET_NONE :{BLACK}None
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle graph of this cargo type STR_SELECTOR_WIDGET_TOOLTIP_ALL :{BLACK}Display all of the items in the list
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING} STR_SELECTOR_WIDGET_TOOLTIP_NONE :{BLACK}Display none of the items in the list
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show detailed performance ratings STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show detailed performance ratings
# Graph key window # Graph key window
STR_GRAPH_KEY_CAPTION :{WHITE}Key to company graphs STR_GRAPH_KEY_CAPTION :{WHITE}Key to company graphs
STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP :{BLACK}Toggle graph of this company STR_GRAPH_CARGO_KEY_CAPTION :{WHITE}Key to cargo graphs
# Company league window # Company league window
STR_COMPANY_LEAGUE_TABLE_CAPTION :{WHITE}Company League Table STR_COMPANY_LEAGUE_TABLE_CAPTION :{WHITE}Company League Table

View File

@ -0,0 +1,375 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file selector_gui.cpp A composable selector widget. */
#include "stdafx.h"
#include "company_base.h"
#include "company_gui.h"
#include "strings_func.h"
#include "table/sprites.h"
#include "selector_gui.h"
#include "widget_type.h"
#include "widgets/selector_widget.h"
#include "zoom_func.h"
#include "safeguards.h"
/**
* Create a new NWidgetBase with all the widgets needed by the SelectorWidget widget
* This function is made to be passed in NWidgetFunction, when creating UI for a parent window
* @see NWidgetFunction
*/
std::unique_ptr<NWidgetBase> SelectorWidget::MakeSelectorWidgetUI()
{
static constexpr NWidgetPart widget_ui[] = {
NWidget(WWT_PANEL, COLOUR_BROWN),
NWidget(WWT_EDITBOX, COLOUR_BROWN, WID_SELECTOR_EDITBOX), SetFill(1, 0), SetResize(1, 0), SetPadding(2),
SetDataTip(STR_LIST_FILTER_TOOLTIP, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
NWidget(NWID_VERTICAL),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_SELECTOR_MATRIX),
SetScrollbar(WID_SELECTOR_SCROLLBAR), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1),
NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_SELECTOR_SCROLLBAR),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SELECTOR_SHOWALL),
SetDataTip(STR_SELECTOR_WIDGET_ALL, STR_SELECTOR_WIDGET_TOOLTIP_ALL), SetResize(1, 0), SetFill(1, 0),
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_SELECTOR_HIDEALL),
SetDataTip(STR_SELECTOR_WIDGET_NONE, STR_SELECTOR_WIDGET_TOOLTIP_NONE), SetResize(1, 0), SetFill(1, 0),
NWidget(WWT_RESIZEBOX, COLOUR_BROWN, WID_SELECTOR_RESIZE), SetDataTip(RWV_SHOW_BEVEL, STR_TOOLTIP_RESIZE), SetResize(0, 0),
EndContainer(),
EndContainer(),
};
return MakeNWidgets(widget_ui, nullptr);
}
/**
* Selector widget initialization function
* This function is meant to be called after CreateNestedTree() of the parent window
*
* @param w The parent window of this widget.
*/
void SelectorWidget::Init(Window *w)
{
this->parent_window = w;
this->vscroll = w->GetScrollbar(WID_SELECTOR_SCROLLBAR);
assert(this->vscroll != nullptr);
RebuildList();
w->querystrings[WID_SELECTOR_EDITBOX] = &this->editbox;
this->vscroll->SetCount(this->filtered_list.size());
this->vscroll->SetCapacityFromWidget(w, WID_SELECTOR_MATRIX);
}
/**
* Selector widget's OnClick event handler
* This function is meant to be called from the parent's window's OnClick
* Event handler
*
* @param pt The click coordinate.
* @param widget The id of the targeted widget.
* @param click_count The number of clicks.
*
* All of these parameters should be passed straight from the parent's window's OnClick
* Event handler, without any changes, or conditional checks
*
* @see Window::OnClick()
*/
void SelectorWidget::OnClick(Point pt, WidgetID widget, [[maybe_unused]]int click_count)
{
switch (widget) {
case WID_SELECTOR_HIDEALL:
for (uint i = 0; i < this->shown.size(); i++) {
this->shown[i] = false;
}
this->OnChanged();
this->parent_window->InvalidateData(0, true);
break;
case WID_SELECTOR_SHOWALL:
for (uint i = 0; i < this->shown.size(); i++) {
this->shown[i] = true;
}
this->OnChanged();
this->parent_window->InvalidateData(0, true);
break;
case WID_SELECTOR_MATRIX: {
size_t pos = vscroll->GetScrolledRowFromWidget(pt.y, this->parent_window, widget);
/* Check if the click was out of range. */
if (pos >= filtered_list.size()) return;
int id = this->filtered_list[pos];
this->shown[id].flip();
this->OnChanged();
this->parent_window->InvalidateData(0, true);
break;
}
}
}
void SelectorWidget::OnMouseOver(Point pt, WidgetID widget)
{
if (widget != WID_SELECTOR_MATRIX) return;
auto it = vscroll->GetScrolledItemFromWidget(this->filtered_list, pt.y, this->parent_window, widget);
/* Check if the hover was out of range. */
if (it == this->filtered_list.end()) return;
/* Check if the selection actually changed. */
if (*it == this->selected_id) return;
this->selected_id = *it;
this->OnChanged();
this->parent_window->InvalidateData(0, true);
}
/**
* Selector widget's OnInvalidateData event handler
* This function is meant to be called from the parent's window's OnInvalidateData
* Event handler. This function will do nothing when runnning ouside of the gui scope.
*
* @param data Information about the changed data.
* @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
*
* All of these parameters should be passed straight from the parent's window's OnInvalidateData
* Event handler, without any changes, or conditional checks
*
* @see Window::OnInvalidateData()
*/
void SelectorWidget::OnInvalidateData([[maybe_unused]] int data, bool gui_scope)
{
if (!gui_scope) return;
RebuildList();
this->vscroll->SetCount(this->filtered_list.size());
this->vscroll->SetCapacityFromWidget(parent_window, WID_SELECTOR_MATRIX);
/* This does not assume that the IDs must be contiguous. */
auto it = std::find(std::begin(this->filtered_list), std::end(this->filtered_list), this->selected_id);
if (it != std::end(this->filtered_list)) {
int selected_pos = std::distance(std::begin(this->filtered_list), it);
this->vscroll->ScrollTowards(selected_pos);
}
}
/**
* Selector widget's UpdateWidgetSize method
* This function is meant to be called from the parent's window's UpdateWidgetSize method
*
* @param widget Widget number.
* @param[In,out] size Size of the widget.
* @param padding Recommended amount of space between the widget content and the widget edge.
* @param[In,out] fill Fill step of the widget.
* @param[In,out] resize Resize step of the widget.
*
* All of these parameters should be passed straight from the parent's window's UpdateWidgetSize
* method, without any changes, or conditional checks.
*
* @see Window::UpdateWidgetSize()
*/
void SelectorWidget::UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize)
{
if (widget != WID_SELECTOR_MATRIX) return;
const int min_rows = 11; ///< The minimal number of rows shown
const int min_width = ScaleGUITrad(100); ///< The minimal width of the widget
this->row_height = GetCharacterHeight(FS_NORMAL) + padding.height;
size.height = this->row_height * min_rows;
size.width = min_width;
resize.width = 1;
resize.height = this->row_height;
fill.width = 1;
fill.height = this->row_height;
}
/**
* Selector widget's OnResize event handler
* This function is meant to be called from the parent's window's OnResize
* Event handler
*
* All of these parameters should be passed straight from the parent's window's OnResize
* Event handler, without any changes, or conditional checks
*
* @see Window::OnResize()
*/
void SelectorWidget::OnResize()
{
this->vscroll->SetCapacityFromWidget(parent_window, WID_SELECTOR_MATRIX);
}
/**
* Selector widget's OnEditboxChanged event handler
* This function is meant to be called from the parent's window's OnEditboxChanged
* Event handler
*
* @param wid The widget id of the editbox.
*
* All of these parameters should be passed straight from the parent's window's OnEditboxChanged
* Event handler, without any changes, or conditional checks
*
* @see Window::OnEditboxChanged()
*/
void SelectorWidget::OnEditboxChanged(WidgetID wid)
{
if (wid != WID_SELECTOR_EDITBOX) return;
this->string_filter.SetFilterTerm(this->editbox.text.buf);
this->parent_window->InvalidateData(0);
this->vscroll->SetCount(this->filtered_list.size());
}
/**
* Selector widget's DrawWidget method
* This function is meant to be called from the parent's window's DrawWidget
* method
*
* @param widget Widget number.
* @param[In,out] size Size of the widget.
* @param padding Recommended amount of space between the widget content and the widget edge.
* @param[In,out] fill Fill step of the widget.
* @param[In,out] resize Resize step of the widget.
*
* All of these parameters should be passed straight from the parent's window's DrawWidget
* Event handler, without any changes, or conditional checks
*
* This function calls a profile defined function this->profile.DrawSection(),
* that actually draws the fields
*
* @see Window::DrawWidget()
* @see SelectorWidget::Profile
*/
void SelectorWidget::DrawWidget(const Rect &r, WidgetID widget)
{
if (widget != WID_SELECTOR_MATRIX) return;
Rect line = r.WithHeight(row_height);
auto [first, last] = this->vscroll->GetVisibleRangeIterators(filtered_list);
for (auto it = first; it != last; ++it) {
const Rect ir = line.Shrink(WidgetDimensions::scaled.framerect);
if (this->shown[*it]) {
DrawFrameRect(line, COLOUR_BROWN, FR_LOWERED);
}
this->DrawSection(*it, ir.Shrink(WidgetDimensions::scaled.matrix));
line = line.Translate(0, row_height);
}
}
/**
* A function that updates and rebuilds the list of selectable items
* This function calls a profile defined function this->profile.RebuildList(),
* that actually rebuilds the list of items.
*
* It is also manages the shown items and the filtered_list lists
*
* @see SelectorWidget::Profile
*/
void SelectorWidget::RebuildList()
{
this->list.clear();
this->filtered_list.clear();
this->PopulateList();
uint32_t max = *std::max_element(std::begin(this->list), std::end(this->list));
this->shown.reserve(max);
for (size_t i = this->shown.size(); i <= max ; i++) {
this->shown.push_back(true);
}
}
/* virtual */ void CargoSelectorWidget::PopulateList()
{
for (const CargoSpec *cargo : _sorted_standard_cargo_specs){
this->list.push_back(cargo->Index());
if (this->string_filter.IsEmpty()) {
this->filtered_list.push_back(cargo->Index());
continue;
}
this->string_filter.ResetState();
SetDParam(0, cargo->name);
this->string_filter.AddLine(GetString(STR_JUST_STRING));
if (this->string_filter.GetState()) {
this->filtered_list.push_back(cargo->Index());
}
}
}
/* virtual */ void CargoSelectorWidget::DrawSection(uint id, const Rect &r)
{
const CargoID cargo_id = static_cast<CargoID>(id); ///< CargoID of the current row's cargo
const CargoSpec *cargo = CargoSpec::Get(id); ///< CargoSpec of the current row's cargo
const bool rtl = _current_text_dir == TD_RTL;
int legend_height = GetCharacterHeight(FS_SMALL);
int legend_width = legend_height * 9 / 6;
Rect cargo_swatch = r.WithWidth(legend_width, rtl);
cargo_swatch.top = CenterBounds(r.top, r.bottom, legend_height) - 1;
cargo_swatch.bottom = cargo_swatch.top + legend_height;
/* Cargo-colour box with outline. */
GfxFillRect(cargo_swatch, PC_BLACK);
GfxFillRect(cargo_swatch.Shrink(WidgetDimensions::scaled.bevel), cargo->legend_colour);
/* Cargo name. */
SetDParam(0, cargo->name);
const Rect text = r.Indent(legend_width + WidgetDimensions::scaled.hsep_wide, rtl);
const TextColour colour = (this->selected_id.value_or(INVALID_OWNER) == cargo_id) ? TC_WHITE : TC_BLACK;
DrawString(text.left, text.right, CenterBounds(text.top, text.bottom, GetCharacterHeight(FS_NORMAL)), STR_JUST_STRING, colour);
}
/* virtual */ void CompanySelectorWidget::PopulateList()
{
for (const Company *c: Company::Iterate()) {
this->list.push_back(c->index);
if (this->string_filter.IsEmpty()) {
this->filtered_list.push_back(c->index);
continue;
}
this->string_filter.ResetState();
SetDParam(0, c->index);
this->string_filter.AddLine(GetString(STR_COMPANY_NAME));
if (this->string_filter.GetState()) {
this->filtered_list.push_back(c->index);
}
}
}
/* virtual */ void CompanySelectorWidget::DrawSection(uint id, const Rect &r)
{
const CompanyID cid = static_cast<CompanyID>(id);
assert(Company::IsValidID(cid));
const bool rtl = _current_text_dir == TD_RTL;
const Dimension icon_size = GetSpriteSize(SPR_COMPANY_ICON);
DrawCompanyIcon(cid, rtl ? r.right - icon_size.width : r.left, CenterBounds(r.top, r.bottom, icon_size.height));
const Rect text = r.Indent(icon_size.width + WidgetDimensions::scaled.hsep_wide, rtl);
SetDParam(0, cid);
const TextColour colour = (this->selected_id.value_or(INVALID_OWNER) == cid) ? TC_WHITE : TC_BLACK;
DrawString(text.left, text.right, CenterBounds(text.top, text.bottom, GetCharacterHeight(FS_NORMAL)), STR_COMPANY_NAME, colour);
}

95
src/selector_gui.h 100644
View File

@ -0,0 +1,95 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file selector_gui.h Functions/types etc. related to the selector widget. */
#ifndef SELECTOR_GUI_H
#define SELECTOR_GUI_H
#include "stdafx.h"
#include "querystring_gui.h"
#include "stringfilter_type.h"
#include "strings_type.h"
#include "widget_type.h"
#include "window_gui.h"
#include "window_type.h"
#include "safeguards.h"
struct SelectorWidget {
std::optional<int> selected_id; ///< ID of the currently selected item
std::vector<bool> shown; ///< A vector that determines which items are shown e.g on the graph. Not to be confused with filtered_list
std::vector<uint32_t> list; ///< A list of all the items
StringFilter string_filter; ///< The filter that checks wether an item should be displayed based on the editbox
/** A list of items displayed in WID_SELECTION_MATRIX after the editbox filtering
* It is a strict subset of SelectorWidget::list */
std::vector<uint32_t> filtered_list;
static std::unique_ptr<NWidgetBase> MakeSelectorWidgetUI();
void Init(Window *w);
void OnClick(Point pt, WidgetID widget, int click_count);
void OnMouseOver(Point pt, WidgetID widget);
void OnInvalidateData(int data, bool gui_scope);
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize);
void OnResize();
void OnEditboxChanged(WidgetID wid);
void DrawWidget(const Rect &r, WidgetID widget);
void RebuildList();
virtual ~SelectorWidget() {};
private:
Window *parent_window; ///< The parent window pointer
int row_height; ///< The height of 1 row in the WID_SELECTOR_MATRIX widget
Scrollbar *vscroll; ///< The vertical scrollbar
/** A QueryString modifiable by the WID_SELECTOR_EDITBOX widget */
QueryString editbox{MAX_LENGTH_COMPANY_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_COMPANY_NAME_CHARS};
/**
* Draws "section" (line) of the scrollable list.
* Called by #DrawWidget
* @param id The "id" of the item to be drawn, can mean different things depending on what the selector widget is displaying.
* e.g when this widget displays companies, the id is a unique and valid CompanyID
* @param r The rectangle where the item is to be drawn.
*
* @see CargoSelectorWidget::DrawSection()
* @see CompanySelectorWidget::DrawSection()
* @see SelectorWidget::PopulateList()
* @see SelectorWidget::DrawWidget()
*/
virtual void DrawSection(uint id, const Rect &r) = 0;
/**
* Repopulates the list that the widget uses to keep track of different selectable items
* @see CargoSelectorWidget::PopulateList()
* @see CompanySelectorWidget::PopulateList()
*/
virtual void PopulateList() = 0;
/**
* Called when the widget selection or visibility of some item is changed.
* Used to update things that depend on the selector widget's data.
*/
virtual void OnChanged() = 0;
};
struct CargoSelectorWidget : SelectorWidget {
virtual void DrawSection(uint id, const Rect &r) override;
virtual void PopulateList() override;
};
struct CompanySelectorWidget : SelectorWidget {
virtual void DrawSection(uint id, const Rect &r) override;
virtual void PopulateList() override;
};
#endif /* SELECTOR_GUI_H */

View File

@ -45,6 +45,7 @@ add_files(
screenshot_widget.h screenshot_widget.h
script_widget.h script_widget.h
settings_widget.h settings_widget.h
selector_widget.h
sign_widget.h sign_widget.h
smallmap_widget.h smallmap_widget.h
station_widget.h station_widget.h

View File

@ -0,0 +1,25 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file selector_widget.h Types related to the selector widget widgets. */
#ifndef WIDGETS_SELECTOR_WIDGET_H
#define WIDGETS_SELECTOR_WIDGET_H
/** Widgets of the #SelectorWidget class. */
enum SelectorClassWindowWidgets : WidgetID {
WID_SELECTOR_START = (1 << 16) + 1000, ///< Dummy to ensure widgets don't overlap.
WID_SELECTOR_MATRIX, ///< Item list.
WID_SELECTOR_SCROLLBAR, ///< Vertical scrollbar.
WID_SELECTOR_EDITBOX, ///< Editbox filter.
WID_SELECTOR_HIDEALL, ///< Hide all button.
WID_SELECTOR_SHOWALL, ///< Show all button.
WID_SELECTOR_RESIZE, ///< Resize handle
};
#endif /* WIDGETS_SELECTOR_WIDGET_H */