diff --git a/doc/type/field.txt b/doc/type/field.txt index e43abd74..eefa4a78 100644 --- a/doc/type/field.txt +++ b/doc/type/field.txt @@ -22,6 +22,7 @@ Fields are part of the [[file:style triangle]]: * @multiple choice@ * @package choice@ * @boolean@ + * @slider@ * @image@ * @symbol@ * @color@ @@ -49,6 +50,7 @@ The @type@ determines what values of this field contain: | @multiple choice@ Zero or more choices from a list A single image or multiple images | @package choice@ A choice from a list of installed [[type:package]]s Text and/or an image | @boolean@ @yes@ or @no@ Text or an image or both +| @slider@ A choice from a list of numbers Text or an image | @color@ Any color or a restricted selection from a list A box filled with the color | @image@ Any image The image | @symbol@ A [[type:symbol]] edited with the symbol editor The image @@ -78,6 +80,18 @@ Additional properties are available, depending on the type of field: These choices must appear in the same order as they do in the @choices@ property. | @"boolean"@ ''A boolean field is a choice field with the choices @"yes"@ and @"no"@.'' <<< <<< <<< + +| @"slider"@ ''A slider field is a choice field where the choices are all numbers.'' <<< <<< <<< +| ^^^ @script@ [[type:script]] Script to apply to values of this field after each change.
+ If the script evaluates to a constant (i.e. doesn't use @value@) then values in this field can effectively not be edited. +| ^^^ @default@ [[type:script]] Script to determine the value when it is in the default state (not edited). +| ^^^ @initial@ [[type:string]] Initial value for new values for this field. Must be a number, otherwise it will be replaced with minimum. +| ^^^ @default name@ [[type:string]] @"Default"@ Name of the default state. +| ^^^ @minimum@ [[type:integer]] Minimum possible value for this field. +| ^^^ @maximum@ [[type:integer]] Maximum possible value for this field. +| ^^^ @increment@ [[type:integer]] Gap between possible values for this field. +| ^^^ @choice colors@ [[type:map]] of opaque [[type:color]]s Colors of the choices for statistics graphs. +| ^^^ @choice colors cardlist@ [[type:map]] of opaque [[type:color]]s Colors of the choices for lines in the card list,
see also the @card list color script@ property of [[type:game]]s. | @"package choice"@ @script@ [[type:script]] Script to apply to values of this field after each change.
diff --git a/resource/slider_center.png b/resource/slider_center.png new file mode 100644 index 00000000..d191b98e Binary files /dev/null and b/resource/slider_center.png differ diff --git a/resource/slider_left.png b/resource/slider_left.png new file mode 100644 index 00000000..127cccec Binary files /dev/null and b/resource/slider_left.png differ diff --git a/resource/slider_right.png b/resource/slider_right.png new file mode 100644 index 00000000..d8204c3b Binary files /dev/null and b/resource/slider_right.png differ diff --git a/resource/slider_tick.png b/resource/slider_tick.png new file mode 100644 index 00000000..73b4c329 Binary files /dev/null and b/resource/slider_tick.png differ diff --git a/resource/win32_res.rc b/resource/win32_res.rc index bbdf8f37..b7e3d252 100644 --- a/resource/win32_res.rc +++ b/resource/win32_res.rc @@ -192,6 +192,11 @@ shape_align_middle IMAGE "shape_align_middle.png" shape_align_center IMAGE "shape_align_center.png" shape_align_both IMAGE "shape_align_both.png" +slider_left IMAGE "slider_left.png" +slider_right IMAGE "slider_right.png" +slider_center IMAGE "slider_center.png" +slider_tick IMAGE "slider_tick.png" + // -------------------------------------------------------- : WX //wxBITMAP_STD_COLOURS BITMAP "wx/msw/colours.bmp" diff --git a/src/data/field.cpp b/src/data/field.cpp index 9e85c63c..36536291 100644 --- a/src/data/field.cpp +++ b/src/data/field.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -80,6 +81,7 @@ intrusive_ptr read_new(Reader& reader) { else if (type == _("choice")) field = make_intrusive(); else if (type == _("multiple choice")) field = make_intrusive(); else if (type == _("boolean")) field = make_intrusive(); + else if (type == _("slider")) field = make_intrusive(); else if (type == _("image")) field = make_intrusive(); else if (type == _("symbol")) field = make_intrusive(); else if (type == _("color")) field = make_intrusive(); diff --git a/src/data/field/slider.cpp b/src/data/field/slider.cpp new file mode 100644 index 00000000..0fafc70e --- /dev/null +++ b/src/data/field/slider.cpp @@ -0,0 +1,75 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) Twan van Laarhoven and the other MSE developers | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : SliderField + +SliderField::SliderField() + : minimum(0) + , maximum(100) + , increment(1) +{} + +IMPLEMENT_FIELD_TYPE(Slider, "slider"); + +IMPLEMENT_REFLECTION(SliderField) { + REFLECT_BASE(Field); // NOTE: don't reflect as a ChoiceField + REFLECT(script); + REFLECT_N("default", default_script); + REFLECT(initial); + REFLECT(minimum); + REFLECT(maximum); + REFLECT(increment); +} + +void SliderField::after_reading(Version ver) { + Field::after_reading(ver); + + if (maximum < minimum) { + int temp = maximum; + maximum = minimum; + minimum = temp; + } + if (increment < 1) increment = 1; + + for (int i = minimum; i < maximum; i += increment) { + choices->choices.push_back(make_intrusive(wxString::Format(wxT("%i"), i))); + } + choices->choices.push_back(make_intrusive(wxString::Format(wxT("%i"), maximum))); + + int initial_int; + try { + initial_int = std::stoi(initial.ToStdString()); + if (initial_int < minimum || initial_int > maximum) initial = wxString::Format(wxT("%i"), minimum); + } + catch (...) { + initial = wxString::Format(wxT("%i"), minimum); + } + choices->initIds(); +} +// ----------------------------------------------------------------------------- : SliderStyle + +SliderStyle::SliderStyle(const ChoiceFieldP& field) + : ChoiceStyle(field) +{ + render_style = RENDER_TEXT; +} + +IMPLEMENT_REFLECTION(SliderStyle) { + REFLECT_BASE(ChoiceStyle); +} + +// ----------------------------------------------------------------------------- : SliderValue + +IMPLEMENT_REFLECTION_NAMELESS(SliderValue) { + REFLECT_BASE(ChoiceValue); +} diff --git a/src/data/field/slider.hpp b/src/data/field/slider.hpp new file mode 100644 index 00000000..8218eee6 --- /dev/null +++ b/src/data/field/slider.hpp @@ -0,0 +1,60 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) Twan van Laarhoven and the other MSE developers | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#pragma once + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : SliderField + +DECLARE_POINTER_TYPE(SliderField); +DECLARE_POINTER_TYPE(SliderStyle); +DECLARE_POINTER_TYPE(SliderValue); + +/// A field whos value is an integer from a range +class SliderField : public ChoiceField { +public: + SliderField(); + DECLARE_FIELD_TYPE(SliderField); + + int minimum, maximum, increment; + + void after_reading(Version ver) override; +}; + +// ----------------------------------------------------------------------------- : SliderStyle + +/// The Style for a SliderField +class SliderStyle : public ChoiceStyle { +public: + SliderStyle(const ChoiceFieldP& field); + DECLARE_HAS_FIELD(Slider); // not DECLARE_STYLE_TYPE, because we use a normal ChoiceValueViewer/Editor + StyleP clone() const override; + + // no extra data + +private: + DECLARE_REFLECTION(); +}; + +// ----------------------------------------------------------------------------- : SliderValue + +/// The Value in a SliderField +class SliderValue : public ChoiceValue { +public: + inline SliderValue(const ChoiceFieldP& field) : ChoiceValue(field) {} + DECLARE_HAS_FIELD(Slider); + ValueP clone() const override; + + // no extra data + +private: + DECLARE_REFLECTION(); +}; + diff --git a/src/gui/drop_down_list.cpp b/src/gui/drop_down_list.cpp index 779f2a1e..4d263a51 100644 --- a/src/gui/drop_down_list.cpp +++ b/src/gui/drop_down_list.cpp @@ -15,6 +15,12 @@ #include #include #include + +bool DropDownList::slider_loaded; +wxBitmap DropDownList::slider_left; +wxBitmap DropDownList::slider_right; +wxBitmap DropDownList::slider_center; +wxBitmap DropDownList::slider_tick; // ----------------------------------------------------------------------------- : DropDownHider @@ -91,7 +97,7 @@ void DropDownList::show(bool in_place, wxPoint pos, RealRect* rect) { onShow(); // find selection selected_item = selection(); - // width + // determine item width size_t count = itemCount(); if (item_size.width == 100) { // not initialized wxClientDC dc(this); @@ -102,16 +108,19 @@ void DropDownList::show(bool in_place, wxPoint pos, RealRect* rect) { item_size.width = max(item_size.width, text_width + icon_size.width + 14); // 14 = room for popup arrow + padding } } - // height - int line_count = 0; - for (size_t i = 0 ; i < count ; ++i) if (lineBelow(i)) line_count += 1; - // size + // determine dropdown size RealSize border_size(2,2); // GetClientSize() - GetSize(), assume 1px borders - RealSize size( - item_size.width + marginW * 2, - item_size.height * count + marginH * 2 + line_count - ); - // placement + RealSize size; + if (is_slider) { + size.height = 70 + marginH * 2; + size.width = min(1000.0, max(150.0, max(100.0 + count, item_size.width + marginW * 2))); + } else { + int line_count = 0; + for (size_t i = 0; i < count; ++i) if (lineBelow(i)) line_count += 1; + size.height = item_size.height * count + marginH * 2 + line_count; + size.width = item_size.width + marginW * 2; + } + // determine placement int parent_height = 0; if (!in_place && viewer) { // Position the drop down list below the editor control (based on the style) @@ -302,17 +311,48 @@ void DropDownList::onPaint(wxPaintEvent&) { void DropDownList::draw(DC& dc) { // Size wxSize cs = dc.GetSize(); + size_t count = itemCount(); // Draw background & frame - dc.SetPen (*wxTRANSPARENT_PEN); + dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); dc.DrawRectangle(0, 0, cs.x, cs.y); - dc.SetFont(*wxNORMAL_FONT); - // Draw items - int y = marginH - visible_start; - size_t count = itemCount(); - for (size_t i = 0 ; i < count ; ++i) { - drawItem(dc, y, i); - y += (int)item_size.height + lineBelow(i); + dc.SetFont(*wxNORMAL_FONT); + if (is_slider) { + // If it's a slider, draw the slider + cs = GetClientSize(); + dc.SetPen(*wxBLACK_PEN); + dc.SetFont(wxFont(12, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, _("Arial"))); + int first_text_width; + dc.GetTextExtent(capitalize(itemText(0)), &first_text_width, nullptr); + int last_text_width; + dc.GetTextExtent(capitalize(itemText(count-1)), &last_text_width, nullptr); + dc.DrawText(capitalize(itemText(0)), marginW + 4, 14); + dc.DrawText(capitalize(itemText(count-1)), cs.x - marginW - 4 - last_text_width, 14); + + int slider_start = first_text_width + marginW + 16; + int slider_end = cs.x - (last_text_width + marginW + 16); + dc.DrawBitmap(slider_left, slider_start, 14); + for (size_t i = slider_start + 19; i < slider_end - 19; i+=19) { + dc.DrawBitmap(slider_center, i, 14); + } + dc.DrawBitmap(slider_right, slider_end - 19, 14); + + int selected_index = selected_item < 0 ? 0 : selected_item; + int slider_pos = round((double)selected_index/(count - 1) * (slider_end - slider_start)) + slider_start; + dc.DrawBitmap(slider_tick, slider_pos - 7, 9); // -7 cause the bitmap is 15 pixels wide + + int selected_text_width; + dc.GetTextExtent(capitalize(itemText(selected_index)), &selected_text_width, nullptr); + dc.DrawText(capitalize(itemText(selected_index)), slider_pos - selected_text_width/2, 44); + + dc.SetFont(*wxNORMAL_FONT); + } else { + // If it's not a slider, draw the list of items + int y = marginH - visible_start; + for (size_t i = 0; i < count; ++i) { + drawItem(dc, y, i); + y += (int)item_size.height + lineBelow(i); + } } } @@ -384,21 +424,35 @@ void DropDownList::onMotion(wxMouseEvent& ev) { ev.Skip(); return; } - // find selected item - int startY = marginH - visible_start; + // find selected item size_t count = itemCount(); - for (size_t i = 0 ; i < count ; ++i) { - int endY = startY + (int)item_size.height; - if (ev.GetY() >= startY && ev.GetY() < endY) { - if (itemEnabled(i)) { - showSubMenu(i, startY); + if (is_slider) { + int first_text_width; + GetTextExtent(capitalize(itemText(0)), &first_text_width, nullptr); + int last_text_width; + GetTextExtent(capitalize(itemText(count - 1)), &last_text_width, nullptr); + int slider_start = first_text_width + marginW + 16; + int slider_end = cs.x - (last_text_width + marginW + 16); + int slider_pos = ev.GetX(); + if (slider_pos < slider_start) slider_pos = slider_start; + if (slider_pos > slider_end) slider_pos = slider_end; + int selected_item = round(((double)(slider_pos - slider_start)) / (slider_end - slider_start) * (count - 1)); + selectItem(selected_item); + } else { + int startY = marginH - visible_start; + for (size_t i = 0; i < count; ++i) { + int endY = startY + (int)item_size.height; + if (ev.GetY() >= startY && ev.GetY() < endY) { + if (itemEnabled(i)) { + showSubMenu(i, startY); + } + selectItem(i); + return; } - selectItem(i); - return; + startY = endY + lineBelow(i); } - startY = endY + lineBelow(i); + hideSubMenu(); } - hideSubMenu(); } void DropDownList::onMouseLeave(wxMouseEvent& ev) { @@ -516,4 +570,4 @@ BEGIN_EVENT_TABLE(DropDownList,wxPopupWindow) EVT_LEAVE_WINDOW (DropDownList::onMouseLeave) EVT_MOUSEWHEEL (DropDownList::onMouseWheel) EVT_SCROLLWIN (DropDownList::onScroll) -END_EVENT_TABLE () +END_EVENT_TABLE () diff --git a/src/gui/drop_down_list.hpp b/src/gui/drop_down_list.hpp index 0ab84828..49810267 100644 --- a/src/gui/drop_down_list.hpp +++ b/src/gui/drop_down_list.hpp @@ -48,7 +48,10 @@ protected: virtual void onHide() {} inline bool isRoot() { return parent_menu == nullptr; } - + + /// Should the list of choices be displayed as a slider (if all choices are numbers) + bool is_slider = false; + // --------------------------------------------------- : Selection static const size_t NO_SELECTION = (size_t)-1; @@ -79,7 +82,13 @@ protected: static const int marginW = 0; static const int marginH = 0; - + + static bool slider_loaded; + static wxBitmap slider_left; + static wxBitmap slider_right; + static wxBitmap slider_center; + static wxBitmap slider_tick; + // may be changed by derived class int text_offset; ///< Vertical distance between top of item and text RealSize item_size; ///< Size of an item; diff --git a/src/gui/value/choice.cpp b/src/gui/value/choice.cpp index 732f4b29..3e0377ab 100644 --- a/src/gui/value/choice.cpp +++ b/src/gui/value/choice.cpp @@ -200,7 +200,29 @@ END_EVENT_TABLE() DropDownChoiceList::DropDownChoiceList(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group) : DropDownChoiceListBase(parent, is_submenu, cve, group) -{} +{ + // determine if slider + size_t count = itemCount(); + if (count > 2) { + int value; + try { + value = std::stoi(itemText(0).ToStdString()); + value = std::stoi(itemText(1).ToStdString()); + value = std::stoi(itemText(count - 1).ToStdString()); + // the choices are numbers, use a slider + is_slider = true; + // load slider images if needed + if (!slider_loaded) { + slider_loaded = true; + slider_left = load_resource_image(_("slider_left")); + slider_right = load_resource_image(_("slider_right")); + slider_center = load_resource_image(_("slider_center")); + slider_tick = load_resource_image(_("slider_tick")); + } + } + catch (...) {} + } +} void DropDownChoiceList::onShow() { DropDownChoiceListBase::onShow();