#include namespace nana { arg_slider::arg_slider(slider& wdg) : widget(wdg) {} namespace drawerbase { namespace slider { class interior_renderer : public renderer { private: virtual void background(window wd, graph_reference graph, bool isglass) { if(!isglass) graph.rectangle(true, API::bgcolor(wd)); } virtual void bar(window, graph_reference graph, const bar_t& bi) { //draw border ::nana::color lt(0x83, 0x90, 0x97), rb(0x9d,0xae,0xc2); graph.frame_rectangle(bi.r, lt, lt, rb, rb); } virtual void adorn(window, graph_reference graph, const adorn_t& ad) { auto len = static_cast(ad.bound.y - ad.bound.x); const auto upperblock = ad.block - ad.block / 2; ::nana::color clr_from(0x84, 0xc5, 0xff), clr_trans(0x0f, 0x41, 0xcd), clr_to(0x6e, 0x96, 0xff); if(ad.horizontal) { graph.gradual_rectangle({ ad.bound.x, ad.fixedpos, len, upperblock }, clr_from, clr_trans, true); graph.gradual_rectangle({ ad.bound.x, ad.fixedpos + static_cast(upperblock), len, ad.block - upperblock }, clr_trans, clr_to, true); } else { graph.gradual_rectangle({ ad.fixedpos, ad.bound.x, upperblock, len }, clr_from, clr_trans, false); graph.gradual_rectangle({ ad.fixedpos + static_cast(upperblock), ad.bound.x, ad.block - upperblock, len }, clr_trans, clr_to, false); } } virtual void adorn_textbox(window, graph_reference graph, const std::string& str, const nana::rectangle & r) { graph.rectangle(r, false, colors::white); graph.string({ r.x + 2, r.y + 1 }, str, colors::white); } virtual void slider(window, graph_reference graph, const slider_t& s) { nana::rectangle r{ graph.size() }; if(s.horizontal) { r.x = s.pos; r.width = s.scale; } else { r.y = s.pos; r.height = s.scale; } graph.round_rectangle(r, 3, 3, colors::black, true, static_cast(0xf0f0f0)); } }; class controller { public: enum class style{horizontal, vertical}; enum class parts{none, bar, slider}; typedef drawer_trigger::graph_reference graph_reference; controller() { other_.wd = nullptr; other_.widget = nullptr; other_.graph = nullptr; proto_.renderer = pat::cloneable(interior_renderer()); attr_.skdir = seekdir::bilateral; attr_.dir = style::horizontal; attr_.vcur = 0; attr_.vmax = 10; attr_.slider_scale = 8; attr_.border = 1; attr_.is_draw_adorn = false; } void seek(seekdir sd) { attr_.skdir = sd; } window handle() const { return other_.wd; } void attached(nana::slider& wd, graph_reference graph) { other_.wd = wd.handle(); other_.widget = &wd; other_.graph = &graph; _m_mk_slider_pos_by_value(); } void detached() { other_.graph = nullptr; } pat::cloneable& ext_renderer() { return proto_.renderer; } void ext_renderer(const pat::cloneable& rd) { proto_.renderer = rd; } void ext_provider(const pat::cloneable& pd) { proto_.provider = pd; } void draw() { if(other_.graph && !other_.graph->size().empty()) { bool is_transparent = (bground_mode::basic == API::effects_bground_mode(other_.wd)); proto_.renderer->background(other_.wd, *other_.graph, is_transparent); _m_draw_objects(); } } void vertical(bool v) { auto dir = (v ? style::vertical : style::horizontal); if(dir != attr_.dir) { attr_.dir = dir; _m_mk_slider_pos_by_value(); this->draw(); } } bool vertical() const { return (style::vertical == attr_.dir); } void vmax(unsigned m) { if(m == 0) m = 1; if(attr_.vmax != m) { attr_.vmax = m; if(attr_.vcur > m) { attr_.vcur = m; _m_emit_value_changed(); } _m_mk_slider_pos_by_value(); draw(); } } unsigned vmax() const { return attr_.vmax; } void vcur(unsigned v) { if(attr_.vmax < v) v = attr_.vmax; if(attr_.vcur != v) { attr_.vcur = v; this->_m_mk_slider_pos_by_value(); draw(); } } unsigned vcur() const { return static_cast(attr_.vcur); } void resize() { _m_mk_slider_pos_by_value(); attr_.adorn_pos = attr_.pos; } parts seek_where(::nana::point pos) const { nana::rectangle r = _m_bar_area(); if(style::vertical == attr_.dir) { std::swap(pos.x, pos.y); std::swap(r.width, r.height); } int sdpos = _m_slider_pos(); if (sdpos <= pos.x && pos.x < sdpos + static_cast(attr_.slider_scale)) return parts::slider; sdpos = static_cast(attr_.slider_scale) / 2; if (sdpos <= pos.x && pos.x < sdpos + static_cast(r.width)) { if(pos.y < r.y + static_cast(r.height)) return parts::bar; } return parts::none; } //set_slider_pos //move the slider to a position where a mouse click on WhereBar. bool set_slider_pos(::nana::point pos) { if(style::vertical == attr_.dir) std::swap(pos.x, pos.y); pos.x -= _m_slider_refpos(); if(pos.x < 0) return false; if(pos.x > static_cast(_m_scale())) pos.x = static_cast(_m_scale()); double attr_pos = attr_.pos; double dx = _m_evaluate_by_seekdir(pos.x); attr_.pos = dx; attr_.adorn_pos = dx; _m_mk_slider_value_by_pos(); return (attr_.pos != attr_pos); } void set_slider_refpos(::nana::point pos) { if(style::vertical == attr_.dir) std::swap(pos.x, pos.y); slider_state_.trace = slider_state_.TraceCapture; slider_state_.snap_pos = static_cast(attr_.pos); slider_state_.refpos = pos; API::set_capture(other_.wd, true); } bool release_slider() { if(slider_state_.trace == slider_state_.TraceCapture) { API::release_capture(other_.wd); if(other_.wd != API::find_window(API::cursor_position())) { slider_state_.trace = slider_state_.TraceNone; attr_.is_draw_adorn = false; } else slider_state_.trace = slider_state_.TraceOver; _m_mk_slider_value_by_pos(); _m_mk_slider_pos_by_value(); return true; } return false; } bool if_trace_slider() const { return (slider_state_.trace == slider_state_.TraceCapture); } bool move_slider(const ::nana::point& pos) { int mpos = (style::horizontal == attr_.dir ? pos.x : pos.y); int adorn_pos = slider_state_.snap_pos + (mpos - slider_state_.refpos.x); if (adorn_pos > 0) { int scale = static_cast(_m_scale()); if (adorn_pos > scale) adorn_pos = scale; } else adorn_pos = 0; double dstpos = _m_evaluate_by_seekdir(adorn_pos); attr_.is_draw_adorn = true; if(dstpos != attr_.pos) { attr_.pos = dstpos; attr_.adorn_pos = dstpos; return true; } return false; } bool move_adorn(const ::nana::point& pos) { double xpos = (style::horizontal == attr_.dir ? pos.x : pos.y); xpos -= _m_slider_refpos(); if(xpos > static_cast(_m_scale())) xpos = static_cast(_m_scale()); int adorn_pos = static_cast(attr_.adorn_pos); xpos = _m_evaluate_by_seekdir(xpos); attr_.adorn_pos = xpos; attr_.is_draw_adorn = true; if(slider_state_.trace == slider_state_.TraceNone) slider_state_.trace = slider_state_.TraceOver; return (adorn_pos != static_cast(xpos)); } unsigned move_step(bool forward) { unsigned cmpvalue = static_cast(attr_.vcur); auto value = cmpvalue; if(forward) { if (value) --value; } else if (value < attr_.vmax) ++value; attr_.vcur = value; if (cmpvalue != value) { _m_mk_slider_pos_by_value(); draw(); _m_emit_value_changed(); } return cmpvalue; } unsigned adorn() const { return _m_value_by_pos(attr_.adorn_pos); } bool reset_adorn() { //Test if the slider is captured, the operation should be ignored. Because the mouse_leave always be generated even through //the slider is captured. if(slider_state_.trace == slider_state_.TraceCapture && (nana::API::capture_window() == this->other_.wd)) return false; slider_state_.trace = slider_state_.TraceNone; attr_.is_draw_adorn = false; if(attr_.adorn_pos != attr_.pos) { attr_.adorn_pos = attr_.pos; return true; } return false; } private: void _m_emit_value_changed() const { other_.widget->events().value_changed.emit(::nana::arg_slider{ *other_.widget }, other_.widget->handle()); } nana::rectangle _m_bar_area() const { auto sz = other_.graph->size(); nana::rectangle r{ sz }; if(style::horizontal == attr_.dir) { r.x = attr_.slider_scale / 2 - attr_.border; r.width = (static_cast(sz.width) > (r.x << 1) ? sz.width - (r.x << 1) : 0); } else { r.y = attr_.slider_scale / 2 - attr_.border; r.height = (static_cast(sz.height) > (r.y << 1) ? sz.height - (r.y << 1) : 0); } return r; } unsigned _m_scale() const { nana::rectangle r = _m_bar_area(); return ((style::horizontal == attr_.dir ? r.width : r.height) - attr_.border * 2); } double _m_evaluate_by_seekdir(double pos) const { switch(attr_.skdir) { case seekdir::backward: if(pos < attr_.pos) pos = attr_.pos; break; case seekdir::forward: if(pos > attr_.pos) pos = attr_.pos; break; default: break; } return (pos < 0 ? 0 : pos); } int _m_slider_refpos() const { return static_cast(attr_.slider_scale / 2); } int _m_slider_pos() const { return static_cast(_m_scale() * attr_.vcur / attr_.vmax); } void _m_mk_slider_value_by_pos() { if(_m_scale()) { auto cmpvalue = static_cast(attr_.vcur); if (style::vertical == attr_.dir) { double scl = _m_scale(); attr_.vcur = (scl - attr_.pos) * attr_.vmax / scl; } else attr_.vcur = (attr_.pos * attr_.vmax / _m_scale()); if (cmpvalue != static_cast(attr_.vcur)) _m_emit_value_changed(); } } void _m_mk_slider_pos_by_value() { attr_.pos = double(_m_scale()) * attr_.vcur / attr_.vmax; if (style::vertical == attr_.dir) attr_.pos = _m_scale() - attr_.pos; if(slider_state_.trace == slider_state_.TraceNone) attr_.adorn_pos = attr_.pos; } unsigned _m_value_by_pos(double pos) const { if(_m_scale()) return static_cast(pos * attr_.vmax / _m_scale()); return 0; } void _m_draw_objects() { renderer::bar_t bar; bar.horizontal = (style::horizontal == attr_.dir); bar.border_size = attr_.border; bar.r = _m_bar_area(); if (bar.r.empty()) return; proto_.renderer->bar(other_.wd, *other_.graph, bar); //adorn renderer::adorn_t adorn; adorn.horizontal = bar.horizontal; if (adorn.horizontal) { adorn.bound.x = bar.r.x + attr_.border; adorn.bound.y = adorn.bound.x + static_cast(attr_.adorn_pos); } else { adorn.bound.y = static_cast(other_.graph->height()) - static_cast(attr_.border + bar.r.y); adorn.bound.x = static_cast(attr_.adorn_pos + attr_.border + bar.r.y); } adorn.vcur_scale = static_cast(attr_.pos); adorn.block = (bar.horizontal ? bar.r.height : bar.r.width) - attr_.border * 2; adorn.fixedpos = static_cast((bar.horizontal ? bar.r.y : bar.r.x) + attr_.border); proto_.renderer->adorn(other_.wd, *other_.graph, adorn); _m_draw_slider(); //adorn textbox if(proto_.provider && attr_.is_draw_adorn) { unsigned vadorn = _m_value_by_pos(attr_.adorn_pos); auto str = proto_.provider->adorn_trace(attr_.vmax, vadorn); if(str.size()) { nana::size ts = other_.graph->text_extent_size(str); ts.width += 6; ts.height += 2; int x, y; const int room = static_cast(attr_.adorn_pos); if(bar.horizontal) { y = adorn.fixedpos + static_cast(adorn.block - ts.height) / 2; x = (room > static_cast(ts.width + 2) ? room - static_cast(ts.width + 2) : room + 2) + _m_slider_refpos(); } else { x = (other_.graph->width() - ts.width) / 2; y = (room > static_cast(ts.height + 2) ? room - static_cast(ts.height + 2) : room + 2) + _m_slider_refpos(); } proto_.renderer->adorn_textbox(other_.wd, *other_.graph, str, {x, y, ts.width, ts.height}); } } } void _m_draw_slider() { renderer::slider_t s; s.pos = static_cast(attr_.pos); s.horizontal = (style::horizontal == attr_.dir); s.scale = attr_.slider_scale; s.border = attr_.border; proto_.renderer->slider(other_.wd, *other_.graph, s); } private: struct other_tag { window wd; nana::slider * widget; paint::graphics * graph; }other_; struct prototype_tag { pat::cloneable renderer; pat::cloneable provider; }proto_; struct attr_tag { seekdir skdir; style dir; unsigned border; unsigned vmax; double vcur; double pos; bool is_draw_adorn; double adorn_pos; unsigned slider_scale; }attr_; struct slider_state_tag { enum t{TraceNone, TraceOver, TraceCapture}; t trace; //true if the mouse press on slider. int snap_pos; nana::point refpos; //a point for slider when the mouse was clicking on slider. slider_state_tag(): trace(TraceNone){} }slider_state_; }; //class trigger trigger::trigger() : impl_(new controller_t) {} trigger::~trigger() { delete impl_; } trigger::controller_t* trigger::ctrl() const { return impl_; } void trigger::attached(widget_reference widget, graph_reference graph) { impl_->attached(static_cast(widget), graph); } void trigger::detached() { impl_->detached(); } void trigger::refresh(graph_reference) { impl_->draw(); } void trigger::mouse_down(graph_reference, const arg_mouse& arg) { using parts = controller_t::parts; auto what = impl_->seek_where(arg.pos); if(parts::bar == what || parts::slider == what) { bool mkdir = impl_->set_slider_pos(arg.pos); impl_->set_slider_refpos(arg.pos); if(mkdir) { impl_->draw(); API::lazy_refresh(); } } } void trigger::mouse_up(graph_reference, const arg_mouse&) { bool mkdraw = impl_->release_slider(); if(mkdraw) { impl_->draw(); API::lazy_refresh(); } } void trigger::mouse_move(graph_reference, const arg_mouse& arg) { bool mkdraw = false; if(impl_->if_trace_slider()) { mkdraw = impl_->move_slider(arg.pos); } else { auto what = impl_->seek_where(arg.pos); if(controller_t::parts::none != what) mkdraw = impl_->move_adorn(arg.pos); else mkdraw = impl_->reset_adorn(); } if(mkdraw) { impl_->draw(); API::lazy_refresh(); } } void trigger::mouse_leave(graph_reference, const arg_mouse&) { if(impl_->reset_adorn()) { impl_->draw(); API::lazy_refresh(); } } void trigger::resized(graph_reference, const arg_resized&) { impl_->resize(); impl_->draw(); API::lazy_refresh(); } //end class trigger }//end namespace slider }//end namespace drawerbase //class slider slider::slider(){} slider::slider(window wd, bool visible) { create(wd, rectangle(), visible); } slider::slider(window wd, const rectangle& r, bool visible) { create(wd, r, visible); } void slider::seek(slider::seekdir sd) { get_drawer_trigger().ctrl()->seek(sd); } void slider::vertical(bool v) { get_drawer_trigger().ctrl()->vertical(v); API::update_window(this->handle()); } bool slider::vertical() const { return get_drawer_trigger().ctrl()->vertical(); } void slider::vmax(unsigned m) { if(this->handle()) { get_drawer_trigger().ctrl()->vmax(m); API::update_window(handle()); } } unsigned slider::vmax() const { if(handle()) return get_drawer_trigger().ctrl()->vmax(); return 0; } void slider::value(unsigned v) { if(handle()) { get_drawer_trigger().ctrl()->vcur(v); API::update_window(handle()); } } unsigned slider::value() const { if(handle()) return get_drawer_trigger().ctrl()->vcur(); return 0; } unsigned slider::move_step(bool forward) { if(handle()) { drawerbase::slider::controller* ctrl = this->get_drawer_trigger().ctrl(); unsigned val = ctrl->move_step(forward); if(val != ctrl->vcur()) API::update_window(handle()); return val; } return 0; } unsigned slider::adorn() const { if(empty()) return 0; return get_drawer_trigger().ctrl()->adorn(); } pat::cloneable& slider::ext_renderer() { return get_drawer_trigger().ctrl()->ext_renderer(); } void slider::ext_renderer(const pat::cloneable& di) { get_drawer_trigger().ctrl()->ext_renderer(di); } void slider::ext_provider(const pat::cloneable& pi) { get_drawer_trigger().ctrl()->ext_provider(pi); } void slider::transparent(bool enabled) { if(enabled) API::effects_bground(*this, effects::bground_transparent(0), 0.0); else API::effects_bground_remove(*this); } bool slider::transparent() const { return (bground_mode::basic == API::effects_bground_mode(*this)); } //end class slider }//end namespace nana