Slider refactor

1) We now use a single function to set slider min/max value, this fixes #1641
   (sliders having a temporary invalid (min > max) state between set_min() and
   set_max() calls)
2) I split the sliders_base class from the scrollbar_base class to not
   accidently break the rather complicated scrollbar_base class while
   refactoring sliders.
3) We don't use floats in the slider code to make sure no rounding errors happen.
4) This fixes #1539 by removing slider::in_orthoginal (which made it impossible
   to pull a slider to its maximum/minimum) and intead clamping the mouse
   position in range.
5) This fixes #1656 and #1767 by refactoriung code (that bug happend mainly
   because the old code used variables that contained the position in 'steps'
   as pixel position and vice versa)
6) Sliders now 'snap' to the correct values just like the gui1 sliders did.
7) Slider positions are now poperly rounded to its neariest values
   instead of just beeing 'floored'
This commit is contained in:
gfgtdf 2017-04-12 18:03:19 +02:00
parent 7474c7f599
commit e9c6dff56a
12 changed files with 806 additions and 151 deletions

View file

@ -331,6 +331,9 @@
<None Include="..\..\src\language_win32.ii" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\gui\widgets\slider_base.cpp">
<ObjectFileName>$(IntDir)Gui\Widgets\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\about.cpp" />
<ClCompile Include="..\..\src\actions\advancement.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Actions\</ObjectFileName>

View file

@ -276,6 +276,7 @@ gui/widgets/scrollbar_panel.cpp
gui/widgets/settings.cpp
gui/widgets/size_lock.cpp
gui/widgets/slider.cpp
gui/widgets/slider_base.cpp
gui/widgets/spacer.cpp
gui/widgets/stacked_widget.cpp
gui/widgets/styled_widget.cpp

View file

@ -320,8 +320,7 @@ public:
model_.previous_page->set_active(has_previous);
model_.next_page->set_active(has_next);
model_.populate_chat_message_list(first, last);
model_.page_number->set_minimum_value(1);
model_.page_number->set_maximum_value(count_of_pages);
model_.page_number->set_value_range(1, count_of_pages);
model_.page_number->set_active(count_of_pages > 1);
LOG_CHAT_LOG << "Maximum value of page number slider: "
<< count_of_pages << std::endl;

View file

@ -80,7 +80,7 @@ void generator_settings::adjust_minimum_size_by_players(window& window)
const auto update_dimension_slider = [&](field_integer* field) {
slider& w = dynamic_cast<slider&>(*field->get_widget());
w.set_minimum_value(min_size + extra_size);
w.set_value_range(min_size + extra_size, w.get_maximum_value());
};
update_dimension_slider(width_);

View file

@ -294,8 +294,7 @@ void mp_options_helper::display_custom_options(const std::string& type, int node
slider* slide;
std::tie(slide, val) = add_node_and_get_widget<slider>(option_node, "option_slider", data, option_cfg);
slide->set_maximum_value(option_cfg["max"].to_int());
slide->set_minimum_value(option_cfg["min"].to_int());
slide->set_value_range(option_cfg["min"].to_int(), option_cfg["max"].to_int());
slide->set_step_size(option_cfg["step"].to_int(1));
slide->set_value(val.to_int());

View file

@ -604,10 +604,8 @@ void preferences_dialog::post_build(window& window)
slider* setter_widget = build_single_widget_instance<slider>("slider", config {"definition", "minimal"});
setter_widget->set_id("setter");
// Maximum must be set first or this will assert
setter_widget->set_maximum_value(option["max"].to_int());
setter_widget->set_minimum_value(option["min"].to_int());
setter_widget->set_step_size(
option["step"].empty() ? 1 : option["step"].to_int());
setter_widget->set_value_range(option["min"].to_int(), option["max"].to_int());
setter_widget->set_step_size(option["step"].to_int(1));
details_grid.swap_child("setter", setter_widget, true);

View file

@ -36,15 +36,9 @@ public:
/** Gets the selected value. */
virtual int get_value() const = 0;
/** Sets the minimum value. */
virtual void set_minimum_value(const int value) = 0;
/** Gets the minimum value. */
virtual int get_minimum_value() const = 0;
/** Sets the maximum value. */
virtual void set_maximum_value(const int value) = 0;
/** Gets the maximum value. */
virtual int get_maximum_value() const = 0;
};

View file

@ -23,6 +23,8 @@
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "sound.hpp"
#include "utils/math.hpp"
#include "gettext.hpp"
#include "wml_exception.hpp"
#include "utils/functional.hpp"
@ -37,9 +39,10 @@ namespace gui2
REGISTER_WIDGET(slider)
slider::slider(const implementation::builder_slider& builder)
: scrollbar_base(builder, get_control_type())
: slider_base(builder, get_control_type())
, best_slider_length_(0)
, minimum_value_(0)
, step_size_(1)
, minimum_value_label_()
, maximum_value_label_()
, value_label_generator_()
@ -72,69 +75,24 @@ point slider::calculate_best_size() const
return result;
}
void slider::set_value(const int value)
void slider::set_value(int value)
{
if(value == get_value()) {
value = utils::clamp(value, minimum_value_, get_maximum_value());
int old_value = get_value();
if(value == old_value) {
return;
}
if(value < minimum_value_) {
set_value(minimum_value_);
} else if(value > get_maximum_value()) {
set_value(get_maximum_value());
} else {
set_item_position(value - minimum_value_);
}
set_slider_position(value / step_size_ - minimum_value_);
fire(event::NOTIFY_MODIFIED, *this, nullptr);
}
void slider::set_minimum_value(const int minimum_value)
{
if(minimum_value == minimum_value_) {
return;
}
/** @todo maybe make it a VALIDATE. */
assert(minimum_value <= get_maximum_value());
const int value = get_value();
const int maximum_value = get_maximum_value();
minimum_value_ = minimum_value;
// The number of items needs to include the begin and end so distance step
// size.
set_item_count(maximum_value - minimum_value_ + get_step_size());
if(value < minimum_value_) {
set_item_position(0);
}
}
void slider::set_maximum_value(const int maximum_value)
{
if(maximum_value == get_maximum_value()) {
return;
}
/** @todo maybe make it a VALIDATE. */
assert(minimum_value_ <= maximum_value);
const int value = get_value();
// The number of items needs to include the begin and end so distance + step
// size.
set_item_count(maximum_value - minimum_value_ + get_step_size());
if(value > maximum_value) {
set_item_position(get_maximum_value());
}
}
t_string slider::get_value_label() const
{
if(value_label_generator_) {
return value_label_generator_(get_item_position(), get_item_count());
return value_label_generator_(get_slider_position(), get_item_count());
} else if(!minimum_value_label_.empty() && get_value() == get_minimum_value()) {
return minimum_value_label_;
} else if(!maximum_value_label_.empty() && get_value() == get_maximum_value()) {
@ -149,18 +107,11 @@ void slider::child_callback_positioner_moved()
sound::play_UI_sound(settings::sound_slider_adjust);
}
unsigned slider::minimum_positioner_length() const
int slider::positioner_length() const
{
const auto conf = cast_config_to<slider_definition>();
assert(conf);
return conf->minimum_positioner_length;
}
unsigned slider::maximum_positioner_length() const
{
const auto conf = cast_config_to<slider_definition>();
assert(conf);
return conf->maximum_positioner_length;
return conf->positioner_length;
}
unsigned slider::offset_before() const
@ -207,51 +158,10 @@ int slider::on_bar(const point& coordinate) const
return 0;
}
bool slider::in_orthogonal_range(const point& coordinate) const
{
return static_cast<size_t>(coordinate.x) < (get_width() - offset_after());
}
#if 0
void slider::update_current_item_mouse_position()
{
point mouse = get_mouse_position();
mouse.x -= get_x();
mouse.y -= get_y();
current_item_mouse_position_ = mouse;
}
/* TODO: this is designed to allow the slider to snap to value on drag. However, it lags behind the
* mouse cursor too much and seems to cause problems with certain slider values. Will have to look
* into this further.
*/
void slider::move_positioner(const int)
{
const int distance_from_last_item =
get_length_difference(current_item_mouse_position_, get_mouse_position_last_move());
if(std::abs(distance_from_last_item) >= get_pixels_per_step()) {
const int steps_traveled = distance_from_last_item / get_pixels_per_step();
set_item_position(get_item_position() + steps_traveled);
update_current_item_mouse_position();
child_callback_positioner_moved();
fire(event::NOTIFY_MODIFIED, *this, nullptr);
// positioner_moved_notifier_.notify();
update_canvas();
}
}
#endif
void slider::update_canvas()
{
// Inherited.
scrollbar_base::update_canvas();
slider_base::update_canvas();
for(auto& tmp : get_canvases()) {
tmp.set_variable("text", wfl::variant(get_value_label()));
@ -264,7 +174,7 @@ void slider::handle_key_decrease(bool& handled)
handled = true;
scroll(scrollbar_base::ITEM_BACKWARDS);
scroll(slider_base::ITEM_BACKWARDS);
}
void slider::handle_key_increase(bool& handled)
@ -273,7 +183,7 @@ void slider::handle_key_increase(bool& handled)
handled = true;
scroll(scrollbar_base::ITEM_FORWARD);
scroll(slider_base::ITEM_FORWARD);
}
void slider::signal_handler_sdl_key_down(const event::ui_event event, bool& handled, const SDL_Keycode key)
@ -322,6 +232,49 @@ void slider::set_value_labels(const std::vector<t_string>& value_labels)
set_value_labels(std::bind(&default_value_label_generator, value_labels, _1, _2));
}
void slider::set_value_range(int min_value, int max_value)
{
// Settng both at once instead of having multiple functions set_min(),
// set_max() ... fixes an old problem where in cases like
// set_min(-10);set_min(-1);
// min and max would tmporarily have invalid values where since the starting max value is 0;
VALIDATE(min_value <= max_value, "invalid slider data");
if (min_value == minimum_value_ && max_value == get_maximum_value()) {
return;
}
int diff = max_value - min_value;
int old_value = get_value();
step_size_ = gcd(diff, step_size_);
minimum_value_ = min_value;
slider_set_item_last(diff / step_size_);
set_value(old_value);
assert(min_value == get_minimum_value());
assert(max_value == get_maximum_value());
}
void slider::set_step_size(int step_size)
{
const int old_min_value = get_minimum_value();
const int old_max_value = get_maximum_value();
const int range_diff = get_item_count() - 1;
const int old_value = get_value();
step_size_ = gcd(range_diff, step_size);
slider_set_item_last(range_diff / step_size_);
set_value(old_value);
assert(old_min_value == get_minimum_value());
assert(old_max_value == get_maximum_value());
}
// }---------- DEFINITION ---------{
slider_definition::slider_definition(const config& cfg)
@ -381,12 +334,11 @@ slider_definition::slider_definition(const config& cfg)
*/
slider_definition::resolution::resolution(const config& cfg)
: resolution_definition(cfg)
, minimum_positioner_length(cfg["minimum_positioner_length"])
, maximum_positioner_length(cfg["maximum_positioner_length"])
, positioner_length(cfg["minimum_positioner_length"])
, left_offset(cfg["left_offset"])
, right_offset(cfg["right_offset"])
{
VALIDATE(minimum_positioner_length, missing_mandatory_wml_key("resolution", "minimum_positioner_length"));
VALIDATE(positioner_length, missing_mandatory_wml_key("resolution", "minimum_positioner_length"));
// Note the order should be the same as the enum state_t is slider.hpp.
state.emplace_back(cfg.child("state_enabled"));
@ -455,7 +407,7 @@ builder_slider::builder_slider(const config& cfg)
, best_slider_length_(cfg["best_slider_length"])
, minimum_value_(cfg["minimum_value"])
, maximum_value_(cfg["maximum_value"])
, step_size_(cfg["step_size"].to_unsigned(1))
, step_size_(cfg["step_size"].to_int(1))
, value_(cfg["value"])
, minimum_value_label_(cfg["minimum_value_label"].t_str())
, maximum_value_label_(cfg["maximum_value_label"].t_str())
@ -476,16 +428,15 @@ widget* builder_slider::build() const
slider* widget = new slider(*this);
widget->set_best_slider_length(best_slider_length_);
widget->set_maximum_value(maximum_value_);
widget->set_minimum_value(minimum_value_);
widget->set_value_range(minimum_value_, maximum_value_);
widget->set_step_size(step_size_);
widget->set_value(value_);
widget->finalize_setup();
if(!value_labels_.empty()) {
VALIDATE(value_labels_.size() == widget->get_item_count(),
_("The number of value_labels and values don't match."));
VALIDATE(value_labels_.size() == static_cast<size_t>(widget->get_item_count()),
_("The number of value_labels and values don't match."));
widget->set_value_labels(value_labels_);

View file

@ -15,7 +15,7 @@
#pragma once
#include "gui/widgets/integer_selector.hpp"
#include "gui/widgets/scrollbar.hpp"
#include "gui/widgets/slider_base.hpp"
#include "gui/core/widget_definition.hpp"
#include "gui/core/window_builder.hpp"
@ -30,7 +30,7 @@ struct builder_slider;
// ------------ WIDGET -----------{
/** A slider. */
class slider : public scrollbar_base, public integer_selector
class slider : public slider_base, public integer_selector
{
friend struct implementation::builder_slider;
@ -47,41 +47,49 @@ public:
/***** ***** ***** ***** Inherited ***** ***** ***** *****/
/** Inherited from integer_selector. */
void set_value(const int value) override;
void set_value(int value) override;
/** Inherited from integer_selector. */
int get_value() const override
{
return minimum_value_ + get_item_position() * get_step_size();
return minimum_value_ + get_slider_position() * get_step_size();
}
/** Inherited from integer_selector. */
void set_minimum_value(const int minimum_value) override;
/** Inherited from integer_selector. */
int get_minimum_value() const override
{
return minimum_value_;
}
/** Inherited from integer_selector. */
void set_maximum_value(const int maximum_value) override;
/** Inherited from integer_selector. */
int get_maximum_value() const override
{
// The number of items needs to include the begin and end so count - 1.
return minimum_value_ + get_item_count() - 1;
return minimum_value_ + slider_get_item_last() * step_size_;
}
/***** ***** ***** setters / getters for members ***** ****** *****/
int get_item_count() const
{
assert(step_size_ != 0);
return slider_get_item_last() * step_size_ + 1;
}
unsigned get_step_size() const
{
return step_size_;
}
void set_step_size(int step_size);
/***** ***** ***** setters / getters for members ***** ****** *****/
void set_best_slider_length(const unsigned length)
{
best_slider_length_ = length;
set_is_dirty(true);
}
void set_value_range(int min_value, int max_value);
void set_minimum_value_label(const t_string& minimum_value_label)
{
minimum_value_label_ = minimum_value_label;
@ -121,10 +129,11 @@ private:
/**
* The minimum value the slider holds.
*
* The maximum value is minimum + item_count_.
* The maximum value is minimum + item_last_.
* The current value is minimum + item_position_.
*/
int minimum_value_;
int step_size_;
/** Inherited from scrollbar_base. */
unsigned get_length() const override
@ -133,10 +142,7 @@ private:
}
/** Inherited from scrollbar_base. */
unsigned minimum_positioner_length() const override;
/** Inherited from scrollbar_base. */
unsigned maximum_positioner_length() const override;
int positioner_length() const override;
/** Inherited from scrollbar_base. */
unsigned offset_before() const override;
@ -150,9 +156,6 @@ private:
/** Inherited from scrollbar_base. */
int on_bar(const point& coordinate) const override;
/** Inherited from scrollbar_base. */
bool in_orthogonal_range(const point& coordinate) const override;
/** Inherited from scrollbar_base. */
int get_length_difference(const point& original, const point& current) const override
{
@ -224,8 +227,7 @@ struct slider_definition : public styled_widget_definition
{
explicit resolution(const config& cfg);
unsigned minimum_positioner_length;
unsigned maximum_positioner_length;
unsigned positioner_length;
unsigned left_offset;
unsigned right_offset;

View file

@ -0,0 +1,353 @@
/*
Copyright (C) 2008 - 2017 by Mark de Wever <koraq@xs4all.nl>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/widgets/slider_base.hpp"
#include "gui/core/log.hpp"
#include "gui/widgets/window.hpp" // Needed for invalidate_layout()
#include "utils/functional.hpp"
#include "utils/math.hpp"
#define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
#define LOG_HEADER LOG_SCOPE_HEADER + ':'
namespace {
int rounded_division(int value, int new_base, int old_base)
{
if (old_base == 0) {
return new_base / 2;
}
else {
return ::rounded_division(value * new_base, old_base);
}
}
}
namespace gui2
{
slider_base::slider_base(const implementation::builder_styled_widget& builder, const std::string& control_type)
: styled_widget(builder, control_type)
, state_(ENABLED)
, item_last_(0)
, item_position_(0)
, drag_initial_mouse_(0, 0)
, drag_initial_position_(0)
, drag_initial_offset_(0)
, positioner_offset_(0)
, positioner_length_(0)
, snap_(true)
{
connect_signal<event::MOUSE_ENTER>(std::bind(
&slider_base::signal_handler_mouse_enter, this, _2, _3, _4));
connect_signal<event::MOUSE_MOTION>(std::bind(
&slider_base::signal_handler_mouse_motion, this, _2, _3, _4, _5));
connect_signal<event::MOUSE_LEAVE>(std::bind(
&slider_base::signal_handler_mouse_leave, this, _2, _3));
connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
&slider_base::signal_handler_left_button_down, this, _2, _3));
connect_signal<event::LEFT_BUTTON_UP>(std::bind(
&slider_base::signal_handler_left_button_up, this, _2, _3));
}
void slider_base::scroll(const scroll_mode scroll)
{
switch(scroll) {
case BEGIN:
set_slider_position(0);
break;
case ITEM_BACKWARDS:
set_slider_position(item_position_ - 1);
break;
case HALF_JUMP_BACKWARDS:
set_slider_position(item_position_ - jump_size() / 2);
break;
case JUMP_BACKWARDS:
set_slider_position(item_position_ - jump_size());
break;
case END:
set_slider_position(item_last_);
break;
case ITEM_FORWARD:
set_slider_position(item_position_ + 1);
break;
case HALF_JUMP_FORWARD:
set_slider_position(item_position_ + jump_size() / 2);
break;
case JUMP_FORWARD:
set_slider_position(item_position_ + jump_size());
break;
default:
assert(false);
}
fire(event::NOTIFY_MODIFIED, *this, nullptr);
}
void slider_base::place(const point& origin, const point& size)
{
// Inherited.
styled_widget::place(origin, size);
recalculate();
}
void slider_base::set_active(const bool active)
{
if(get_active() != active) {
set_state(active ? ENABLED : DISABLED);
}
}
bool slider_base::get_active() const
{
return state_ != DISABLED;
}
unsigned slider_base::get_state() const
{
return state_;
}
void slider_base::set_slider_position(int item_position)
{
// Set the value always execute since we update a part of the state.
item_position_ = utils::clamp(item_position, 0, item_last_);
// Determine the pixel offset of the item position.
positioner_offset_ = rounded_division(item_position_, max_offset(), item_last_) + offset_before();
update_canvas();
}
void slider_base::update_canvas()
{
for(auto & tmp : get_canvases())
{
tmp.set_variable("positioner_offset", wfl::variant(positioner_offset_));
tmp.set_variable("positioner_length", wfl::variant(positioner_length_));
}
set_is_dirty(true);
}
void slider_base::set_state(const state_t state)
{
if(state != state_) {
state_ = state;
set_is_dirty(true);
}
}
void slider_base::recalculate()
{
// We can be called before the size has been set up in that case we can't do
// the proper recalcultion so stop before we die with an assert.
if(!get_length()) {
return;
}
assert(available_length() > 0);
// All visible.
if(item_last_ == 0) {
positioner_offset_ = offset_before();
recalculate_positioner();
item_position_ = 0;
update_canvas();
return;
}
recalculate_positioner();
set_slider_position(item_position_);
}
void slider_base::move_positioner(int new_offset)
{
int max_offset = this->max_offset();
new_offset = utils::clamp(new_offset, 0, max_offset);
slider_base::slider_position_t final_offset = {new_offset, max_offset};
update_slider_position(final_offset);
assert(final_offset.max_offset == max_offset);
positioner_offset_ = final_offset.offset + offset_before();
update_canvas();
}
void slider_base::update_slider_position(slider_base::slider_position_t& pos)
{
int new_position = rounded_division(pos.offset, item_last_, pos.max_offset);
if(snap_) {
pos.offset = rounded_division(new_position, pos.max_offset, item_last_);
}
if(new_position != item_position_) {
item_position_ = new_position;
child_callback_positioner_moved();
fire(event::NOTIFY_MODIFIED, *this, nullptr);
}
}
void slider_base::signal_handler_mouse_enter(const event::ui_event event,
bool& handled,
bool& halt)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
// Send the motion under our event id to make debugging easier.
signal_handler_mouse_motion(event, handled, halt, get_mouse_position());
}
void slider_base::signal_handler_mouse_motion(const event::ui_event event,
bool& handled,
bool& halt,
const point& coordinate)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << " at " << coordinate << ".\n";
point mouse = coordinate;
mouse.x -= get_x();
mouse.y -= get_y();
switch(state_) {
case ENABLED:
if(on_positioner(mouse)) {
set_state(FOCUSED);
}
break;
case PRESSED: {
move_positioner(get_length_difference(drag_initial_mouse_, mouse) + drag_initial_offset_);
} break;
case FOCUSED:
if(!on_positioner(mouse)) {
set_state(ENABLED);
}
break;
case DISABLED:
// Shouldn't be possible, but seems to happen in the lobby
// if a resize layout happens during dragging.
halt = true;
break;
default:
assert(false);
}
handled = true;
}
void slider_base::signal_handler_mouse_leave(const event::ui_event event,
bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
if(state_ == FOCUSED) {
set_state(ENABLED);
}
handled = true;
}
void slider_base::signal_handler_left_button_down(const event::ui_event event,
bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
point mouse = get_mouse_position();
mouse.x -= get_x();
mouse.y -= get_y();
if(on_positioner(mouse)) {
assert(get_window());
drag_initial_mouse_ = mouse;
drag_initial_position_ = item_position_;
drag_initial_offset_ = positioner_offset_ - offset_before();
get_window()->mouse_capture();
set_state(PRESSED);
}
const int bar = on_bar(mouse);
if(bar == -1) {
scroll(JUMP_BACKWARDS);
} else if(bar == 1) {
scroll(JUMP_FORWARD);
} else {
assert(bar == 0);
}
handled = true;
}
void slider_base::signal_handler_left_button_up(const event::ui_event event,
bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
point mouse = get_mouse_position();
mouse.x -= get_x();
mouse.y -= get_y();
if(state_ != PRESSED) {
return;
}
assert(get_window());
get_window()->mouse_capture(false);
if(on_positioner(mouse)) {
set_state(FOCUSED);
} else {
set_state(ENABLED);
}
drag_initial_mouse_ = {0, 0};
drag_initial_position_ = 0;
drag_initial_offset_ = 0;
handled = true;
}
void slider_base::finalize_setup()
{
// These values won't change so set them once.
for(auto& tmp : get_canvases()) {
tmp.set_variable("offset_before", wfl::variant(offset_before()));
tmp.set_variable("offset_after", wfl::variant(offset_after()));
}
}
} // namespace gui2

View file

@ -0,0 +1,343 @@
/*
Copyright (C) 2008 - 2017 by Mark de Wever <koraq@xs4all.nl>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program 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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "gui/core/notifier.hpp"
#include "gui/widgets/styled_widget.hpp"
#include "utils/functional.hpp"
namespace gui2
{
/**
* Base class for a scroll bar.
*
* class will be subclassed for the horizontal and vertical scroll bar.
* It might be subclassed for a slider class.
*
* To make this class generic we talk a lot about offset and length and use
* pure virtual functions. The classes implementing us can use the heights or
* widths, whichever is applicable.
*
* The NOTIFY_MODIFIED event is send when the position of slider is changed.
*
* Common signal handlers:
* - connect_signal_notify_modified
*/
class slider_base : public styled_widget
{
/** @todo Abstract the code so this friend is no longer needed. */
friend class slider;
public:
slider_base(const implementation::builder_styled_widget& builder, const std::string& control_type);
/**
* scroll 'step size'.
*
* When scrolling we always scroll a 'fixed' amount, these are the
* parameters for these amounts.
*/
enum scroll_mode {
BEGIN, /**< Go to begin position. */
ITEM_BACKWARDS, /**< Go one item towards the begin. */
HALF_JUMP_BACKWARDS, /**< Go half the visible items towards the begin.
*/
JUMP_BACKWARDS, /**< Go the visibile items towards the begin. */
END, /**< Go to the end position. */
ITEM_FORWARD, /**< Go one item towards the end. */
HALF_JUMP_FORWARD, /**< Go half the visible items towards the end. */
JUMP_FORWARD
}; /**< Go the visible items towards the end. */
///container for the current position of a slider.
struct slider_position_t
{
int offset;
int max_offset;
};
/**
* Sets the item position.
*
* We scroll a predefined step.
*
* @param scroll 'step size' to scroll.
*/
void scroll(const scroll_mode scroll);
protected:
/** Is the positioner at the beginning of the slider? */
bool at_begin() const
{
return item_position_ == 0;
}
/**
* Is the positioner at the and of the slider?
*
* Note both begin and end might be true at the same time.
*/
bool at_end() const
{
return item_position_ >= item_last_;
}
void update_slider_position(slider_position_t& pos);
public:
void finalize_setup();
/***** ***** ***** ***** layout functions ***** ***** ***** *****/
/** See @ref widget::place. */
virtual void place(const point& origin, const point& size) override;
/***** ***** ***** ***** Inherited ***** ***** ***** *****/
/** See @ref styled_widget::set_active. */
virtual void set_active(const bool active) override;
/** See @ref styled_widget::get_active. */
virtual bool get_active() const override;
/** See @ref styled_widget::get_state. */
virtual unsigned get_state() const override;
/**
* Possible states of the widget.
*
* Note the order of the states must be the same as defined in settings.hpp.
*/
enum state_t {
ENABLED,
DISABLED,
PRESSED,
FOCUSED,
COUNT
};
protected:
/***** ***** ***** setters / getters for members ***** ****** *****/
void slider_set_item_last(const unsigned item_last)
{
item_last_ = item_last;
recalculate();
}
unsigned slider_get_item_count() const
{
return item_last_ + 1;
}
unsigned slider_get_item_last() const
{
return item_last_;
}
/**
* Note the position isn't guaranteed to be the wanted position
* the step size is honored. The value will be rouded down.
*/
void set_slider_position(int item_position);
unsigned get_slider_position() const
{
return item_position_;
}
unsigned get_positioner_offset() const
{
return positioner_offset_;
}
unsigned get_positioner_length() const
{
return positioner_length_;
}
/**
* See @ref styled_widget::update_canvas.
*
* After a recalculation the canvasses also need to be updated.
*/
virtual void update_canvas() override;
/**
* Callback for subclasses to get notified about positioner movement.
*
* @todo This is a kind of hack due to the fact there's no simple
* callback slot mechanism. See whether we can implement a generic way to
* attach callback which would remove quite some extra code.
*/
virtual void child_callback_positioner_moved()
{
}
virtual int jump_size() const { return 1; };
private:
void set_state(const state_t state);
/**
* Current state of the widget.
*
* The state of the widget determines what to render and how the widget
* reacts to certain 'events'.
*/
state_t state_;
/** one less than the number items the slider 'holds'. */
int item_last_;
/** The item the positioner is at, starts at 0. */
int item_position_;
/**
* The position the mouse was when draggin the slider was started.
*
* This is used during dragging the positioner.
*/
point drag_initial_mouse_;
/**
* The position the slider was when draggin the slider was started.
*
* This is used during dragging the positioner.
*/
int drag_initial_position_;
/**
* The offset in pixels the slider was when dragging the positioner was started.
*/
int drag_initial_offset_;
point mouse2_;
/**
* The start offset of the positioner.
*
* This takes the offset before in consideration.
*/
int positioner_offset_;
/** The current length of the positioner. */
int positioner_length_;
/** whether the slider shoudl 'snap' into its supported values or not */
bool snap_;
/***** ***** ***** ***** Pure virtual functions ***** ***** ***** *****/
/** Get the length of the slider. */
virtual unsigned get_length() const = 0;
int available_length() const { return get_length() - offset_before() - offset_after(); }
int max_offset() const { return std::max(0, available_length() - positioner_length()); }
/**
* The number of pixels we can't use since they're used for borders.
*
* These are the pixels before the widget (left side if horizontal,
* top side if vertical).
*/
virtual unsigned offset_before() const = 0;
/**
* The number of pixels we can't use since they're used for borders.
*
* These are the pixels after the widget (right side if horizontal,
* bottom side if vertical).
*/
virtual unsigned offset_after() const = 0;
/**
* Is the coordinate on the positioner?
*
* @param coordinate Coordinate to test whether it's on the
* positioner.
*
* @returns Whether the location on the positioner is.
*/
virtual bool on_positioner(const point& coordinate) const = 0;
/**
* Is the coordinate on the bar?
*
* @param coordinate Coordinate to test whether it's on the
* bar.
*
* @returns Whether the location on the bar is.
* @retval -1 Coordinate is on the bar before positioner.
* @retval 0 Coordinate is not on the bar.
* @retval 1 Coordinate is on the bar after the positioner.
*/
virtual int on_bar(const point& coordinate) const = 0;
/**
* Gets the relevant difference in between the two positions.
*
* This function is used to determine how much the positioner needs to be
* moved.
*/
virtual int get_length_difference(const point& original,
const point& current) const = 0;
/***** ***** ***** ***** Private functions ***** ***** ***** *****/
/**
* Updates the slider.
*
* Needs to be called when someting changes eg number of items
* or available size. It can only be called once we have a size
* otherwise we can't calulate a thing.
*/
void recalculate();
/**
* Updates the positioner.
*
* This is a helper for recalculate().
*/
virtual int positioner_length() const = 0;
void recalculate_positioner() { positioner_length_ = positioner_length(); }
/**
* Moves the positioner.
*
* @param distance The new offset in pixels of the positioner;
*/
void move_positioner(int offset);
/***** ***** ***** signal handlers ***** ****** *****/
void signal_handler_mouse_enter(const event::ui_event event,
bool& handled,
bool& halt);
void signal_handler_mouse_motion(const event::ui_event event,
bool& handled,
bool& halt,
const point& coordinate);
void signal_handler_mouse_leave(const event::ui_event event, bool& handled);
void signal_handler_left_button_down(const event::ui_event event,
bool& handled);
void signal_handler_left_button_up(const event::ui_event event,
bool& handled);
};
} // namespace gui2

View file

@ -296,6 +296,18 @@ inline unsigned int count_leading_ones(N n) {
return count_leading_zeros<N>(~n);
}
inline int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
//Probably not postable.
inline int rounded_division(int a, int b)
{
auto res = std::div(a,b);
return 2 * res.rem > b ? (res.quot + 1) : res.quot;
}
#if 1
typedef int32_t fixed_t;
# define fxp_shift 8