From c435423919a20a87d100e1ee4cd1fc6ce223040c Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Tue, 25 Dec 2007 20:41:08 +0000 Subject: [PATCH] Pass coordinates relative to the receiving widget's geometry Add List widget Make Indicator and List loadable from datafiles Add Logic and Connector Refactor some common components of Sliders to the base class Allow specifying minimum sizes for parts --- source/button.cpp | 6 +- source/connector.cpp | 47 ++++++++ source/connector.h | 93 +++++++++++++++ source/geometry.cpp | 16 ++- source/geometry.h | 1 + source/hslider.cpp | 32 +++--- source/hslider.h | 8 +- source/list.cpp | 261 +++++++++++++++++++++++++++++++++++++++++++ source/list.h | 75 +++++++++++++ source/logic.cpp | 38 +++++++ source/logic.h | 59 ++++++++++ source/panel.cpp | 38 +++++-- source/part.cpp | 7 ++ source/part.h | 1 + source/slider.cpp | 22 +++- source/slider.h | 9 ++ source/vslider.cpp | 36 +++--- source/vslider.h | 8 +- source/widget.cpp | 9 +- source/widget.h | 7 +- 20 files changed, 711 insertions(+), 62 deletions(-) create mode 100644 source/connector.cpp create mode 100644 source/connector.h create mode 100644 source/list.cpp create mode 100644 source/list.h create mode 100644 source/logic.cpp create mode 100644 source/logic.h diff --git a/source/button.cpp b/source/button.cpp index 58e69bf..bc6fd27 100644 --- a/source/button.cpp +++ b/source/button.cpp @@ -23,9 +23,9 @@ void Button::set_text(const std::string &t) text=t; } -void Button::button_press(int x, int y, unsigned btn) +void Button::button_press(int, int, unsigned btn) { - if(geom.is_inside(x, y) && btn==1) + if(btn==1) state=ACTIVE; } @@ -33,7 +33,7 @@ void Button::button_release(int x, int y, unsigned btn) { if(btn==1) { - if(geom.is_inside(x, y)) + if(geom.is_inside_relative(x, y)) { state=HOVER; signal_clicked.emit(); diff --git a/source/connector.cpp b/source/connector.cpp new file mode 100644 index 0000000..837ec80 --- /dev/null +++ b/source/connector.cpp @@ -0,0 +1,47 @@ +/* $Id$ + +This file is part of libmspgltk +Copyright © 2007 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include "connector.h" +#include "logic.h" + +using namespace std; + +namespace Msp { +namespace GLtk { + +Connector::~Connector() +{ + for(map::iterator i=actions.begin(); i!=actions.end(); ++i) + delete i->second; +} + +void Connector::connect(const Logic &logic) +{ + const list &logic_acts=logic.get_actions(); + + for(list::const_iterator i=logic_acts.begin(); i!=logic_acts.end(); ++i) + { + map::const_iterator j=actions.find(i->type); + if(j!=actions.end()) + j->second->connect(*this, *i->wdg, i->data); + } +} + +void Connector::add(const string &type, ConnAction *act) +{ + map::iterator i=actions.find(type); + if(i!=actions.end()) + { + delete i->second; + i->second=act; + } + else + actions[type]=act; +} + +} // namespace GLtk +} // namespace Msp diff --git a/source/connector.h b/source/connector.h new file mode 100644 index 0000000..a936648 --- /dev/null +++ b/source/connector.h @@ -0,0 +1,93 @@ +/* $Id$ + +This file is part of libmspgltk +Copyright © 2007 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#ifndef MSP_GLTK_CONNECTOR_H_ +#define MSP_GLTK_CONNECTOR_H_ + +#include +#include + +namespace Msp { +namespace GLtk { + +class Connector; +class Logic; +class Widget; + +class ConnAction +{ +public: + virtual void connect(Connector &conn, Widget &wdg, const std::string &data) const =0; + virtual ~ConnAction() { } +}; + +template +class ConnFunc0: public ConnAction +{ +public: + typedef void (C::*FuncType)(W &); + + ConnFunc0(FuncType f): func(f) { } + virtual void connect(Connector &conn, Widget &wdg, const std::string &) const + { + (dynamic_cast(conn).*func)(dynamic_cast(wdg)); + } + +private: + FuncType func; +}; + +template +class ConnFunc1: public ConnAction +{ +public: + typedef void (C::*FuncType)(W &, const std::string &); + + ConnFunc1(FuncType f): func(f) { } + virtual void connect(Connector &conn, Widget &wdg, const std::string &data) const + { + (dynamic_cast(conn).*func)(dynamic_cast(wdg), data); + } + +private: + FuncType func; +}; + +/** +Provides an interface for associating the actions stored in a Logic object with +actual code. Derive a class from this and use the add functions to specify +handlers for each action type. +*/ +class Connector +{ +private: + std::map actions; + +protected: + Connector() { } +public: + virtual ~Connector(); + + void connect(const Logic &); + +protected: + template + void add(const std::string &type, void (C::*func)(W &)) + { add(type, new ConnFunc0(func)); } + + template + void add(const std::string &type, void (C::*func)(W &, const std::string &)) + { add(type, new ConnFunc1(func)); } + +private: + void add(const std::string &, ConnAction *); +}; + +} // namespace GLtk +} // namespace Msp + +#endif diff --git a/source/geometry.cpp b/source/geometry.cpp index 126a0fc..8202774 100644 --- a/source/geometry.cpp +++ b/source/geometry.cpp @@ -15,6 +15,11 @@ bool Geometry::is_inside(int x_, int y_) const return (x_>=x && x_(w) && y_>=y && y_(h)); } +bool Geometry::is_inside_relative(int x_, int y_) const +{ + return (x_>=0 && x_(w) && y_>=0 && y_(h)); +} + Sides::Sides(): top(0), @@ -47,13 +52,16 @@ void Alignment::apply(Geometry &geom, const Geometry &parent) const void Alignment::apply(Geometry &geom, const Geometry &parent, const Sides &margin) const { + unsigned pw=parent.w-margin.left-margin.right; + unsigned ph=parent.h-margin.bottom-margin.top; + if(parent.w>geom.w) - geom.w+=static_cast((parent.w-geom.w)*w); + geom.w+=static_cast((pw-geom.w)*w); if(parent.h>geom.h) - geom.h+=static_cast((parent.h-geom.h)*h); + geom.h+=static_cast((ph-geom.h)*h); - geom.x+=static_cast(margin.left+(parent.w-margin.left-margin.right-geom.w)*x); - geom.y+=static_cast(margin.bottom+(parent.h-margin.bottom-margin.top-geom.h)*y); + geom.x+=static_cast(margin.left+(pw-geom.w)*x); + geom.y+=static_cast(margin.bottom+(ph-geom.h)*y); } } // namespace GLtk diff --git a/source/geometry.h b/source/geometry.h index 6fa8e7f..08d660e 100644 --- a/source/geometry.h +++ b/source/geometry.h @@ -24,6 +24,7 @@ struct Geometry Geometry(): x(0), y(0), w(1), h(1) { } Geometry(int x_, int y_, unsigned w_, unsigned h_): x(x_), y(y_), w(w_), h(h_) { } bool is_inside(int, int) const; + bool is_inside_relative(int, int) const; }; diff --git a/source/hslider.cpp b/source/hslider.cpp index 3b68dac..32d0f65 100644 --- a/source/hslider.cpp +++ b/source/hslider.cpp @@ -16,8 +16,7 @@ namespace Msp { namespace GLtk { HSlider::HSlider(const Resources &r): - Slider(r), - dragging(false) + Slider(r) { update_style(); } @@ -26,14 +25,11 @@ void HSlider::button_press(int x, int y, unsigned btn) { if(geom.is_inside(x, y)) { - int sw=get_slider_width(); - int sx=geom.x+static_cast((geom.w-sw)*(value-min)/(max-min)); - if(btn==1 && x>=sx && x((geom.w-slider_size)*(value-min)/(max-min)); + if(btn==1 && x>=sx && x(sx+slider_size)) { - dragging=true; state=ACTIVE; - drag_start_x=x; - drag_start_value=value; + start_drag(x); } } } @@ -42,7 +38,7 @@ void HSlider::button_release(int, int, unsigned btn) { if(btn==1) { - dragging=false; + end_drag(); state=NORMAL; } } @@ -50,9 +46,7 @@ void HSlider::button_release(int, int, unsigned btn) void HSlider::pointer_motion(int x, int) { if(dragging) - { - set_value(drag_start_value+(x-drag_start_x)*(max-min)/(geom.w-get_slider_width())); - } + drag(x); } void HSlider::render_special(const Part &part) const @@ -60,7 +54,8 @@ void HSlider::render_special(const Part &part) const if(part.get_name()=="slider") { Alignment align=part.get_alignment(); - align.x=(value-min)/(max-min); + if(max>min) + align.x=(value-min)/(max-min); Geometry pgeom=part.get_geometry(); align.apply(pgeom, geom, part.get_margin()); @@ -72,13 +67,18 @@ void HSlider::render_special(const Part &part) const } } -unsigned HSlider::get_slider_width() const +void HSlider::on_geometry_change() +{ + drag_area_size=geom.w-slider_size; +} + +void HSlider::on_style_change() { for(PartSeq::const_iterator i=style->get_parts().begin(); i!=style->get_parts().end(); ++i) if(i->get_name()=="slider") - return i->get_geometry().w; + slider_size=i->get_geometry().w; - return 0; + on_geometry_change(); } } // namespace GLtk diff --git a/source/hslider.h b/source/hslider.h index 8f0be9f..5da211d 100644 --- a/source/hslider.h +++ b/source/hslider.h @@ -20,9 +20,7 @@ the current value of the widget. class HSlider: public Slider { private: - bool dragging; - int drag_start_x; - double drag_start_value; + unsigned slider_size; public: HSlider(const Resources &); @@ -32,7 +30,9 @@ public: private: virtual const char *get_class() const { return "hslider"; } virtual void render_special(const Part &) const; - unsigned get_slider_width() const; + + virtual void on_geometry_change(); + virtual void on_style_change(); }; } // namespace GLtk diff --git a/source/list.cpp b/source/list.cpp new file mode 100644 index 0000000..73f427b --- /dev/null +++ b/source/list.cpp @@ -0,0 +1,261 @@ +/* $Id$ + +This file is part of libmspgltk +Copyright © 2007 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#include +#include "graphic.h" +#include "list.h" +#include "part.h" +#include "style.h" +#include "vslider.h" + +using namespace std; + +namespace Msp { +namespace GLtk { + +List::List(const Resources &r): + Widget(r), + sel_index(-1), + first(0), + n_visible(1), + items_part(0), + slider(new VSlider(res)), + slider_active(false) +{ + slider->set_step(1); + slider->signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed)); + + update_style(); +} + +void List::append(const string &v) +{ + items.push_back(v); + recalculate_parameters(); +} + +void List::insert(unsigned i, const string &v) +{ + if(i>items.size()) + throw InvalidParameterValue("Index out of range"); + + items.insert(items.begin()+i, v); + recalculate_parameters(); +} + +void List::remove(unsigned i) +{ + if(i>items.size()) + throw InvalidParameterValue("Index out of range"); + + items.erase(items.begin()+i); + if(sel_index>static_cast(i)) + --sel_index; + else if(sel_index==static_cast(i)) + sel_index=-1; + + recalculate_parameters(); +} + +void List::clear() +{ + items.clear(); + sel_index=-1; +} + +const string &List::get_selected() const +{ + if(sel_index<0) + throw InvalidState("No selection"); + + return items[sel_index]; +} + +void List::button_press(int x, int y, unsigned btn) +{ + if(slider->get_geometry().is_inside(x, y)) + { + const Geometry &sgeom=slider->get_geometry(); + slider->button_press(x-sgeom.x, y-sgeom.y, btn); + slider_active=true; + } + else if(btn==1) + { + const GL::Font *const font=style->get_font(); + const unsigned row_height=static_cast(font->get_default_size()); + + if(items_part) + y+=items_part->get_margin().top; + + unsigned i=(geom.h-1-y)/row_height; + if(iget_geometry(); + slider->button_release(x-sgeom.x, y-sgeom.y, btn); + slider_active=false; + } +} + +void List::pointer_motion(int x, int y) +{ + if(slider_active) + { + const Geometry &sgeom=slider->get_geometry(); + slider->pointer_motion(x-sgeom.x, y-sgeom.y); + } +} + +void List::render_special(const Part &part) const +{ + if(part.get_name()=="items") + { + const GL::Font *const font=style->get_font(); + const float font_size=font->get_default_size(); + const unsigned row_height=static_cast(font_size); + const Sides &margin=part.get_margin(); + + Geometry pgeom=geom; + pgeom.h=row_height; + + for(unsigned i=0; (i(font->get_string_width(items[first+i])*font_size); + rgeom.h=static_cast((font->get_ascent()-font->get_descent())*font_size); + rgeom.y=geom.h-margin.top-(i+1)*row_height-static_cast(font->get_descent()*font_size); + part.get_alignment().apply(rgeom, pgeom); + + GL::push_matrix(); + GL::translate(rgeom.x, rgeom.y, 0); + GL::scale_uniform(font_size); + font->draw_string(items[first+i]); + GL::pop_matrix(); + } + } + else if(part.get_name()=="selection") + { + if(sel_index>=static_cast(first) && sel_index(first+n_visible)) + { + const GL::Font *const font=style->get_font(); + const float font_size=font->get_default_size(); + const unsigned row_height=static_cast(font_size); + const Sides &margin=part.get_margin(); + + Geometry pgeom=geom; + pgeom.h=row_height; + pgeom.w-=margin.left+margin.right; + + Geometry rgeom=part.get_geometry(); + rgeom.y+=geom.h-margin.top-row_height*(sel_index-first+1); + rgeom.x+=margin.left; + part.get_alignment().apply(rgeom, pgeom); + + GL::push_matrix(); + GL::translate(rgeom.x, rgeom.y, 0); + part.get_graphic(state)->render(rgeom.w, rgeom.h); + GL::pop_matrix(); + } + } + else if(part.get_name()=="slider") + slider->render(); +} + +void List::on_geometry_change() +{ + reposition_slider(); + + recalculate_parameters(); +} + +void List::on_style_change() +{ + reposition_slider(); + + items_part=0; + for(list::const_iterator i=style->get_parts().begin(); i!=style->get_parts().end(); ++i) + if(i->get_name()=="items") + items_part=&*i; + + recalculate_parameters(); +} + +void List::reposition_slider() +{ + for(list::const_iterator i=style->get_parts().begin(); i!=style->get_parts().end(); ++i) + if(i->get_name()=="slider") + { + Geometry sgeom=i->get_geometry(); + i->get_alignment().apply(sgeom, geom, i->get_margin()); + slider->set_geometry(sgeom); + } +} + +void List::recalculate_parameters() +{ + const GL::Font *font=style->get_font(); + unsigned row_height=static_cast(font->get_default_size()); + + unsigned h=geom.h; + if(items_part) + { + const Sides &margin=items_part->get_margin(); + h-=margin.top+margin.bottom; + } + + n_visible=h/row_height; + + if(first+n_visible>items.size()) + { + if(items.size()>n_visible) + first=items.size()-n_visible; + else + first=0; + } + + if(items.size()>n_visible) + { + slider->set_range(0, items.size()-n_visible); + slider->set_value(items.size()-n_visible-first); + } + else + { + slider->set_range(0, 0); + slider->set_value(0); + } +} + +void List::slider_value_changed(double value) +{ + first=items.size()-n_visible-static_cast(value); +} + + +List::Loader::Loader(List &l): + Widget::Loader(l) +{ + add("item", &Loader::item); +} + +void List::Loader::item(const string &v) +{ + static_cast(wdg).append(v); +} + +} // namespace GLtk +} // namespace Msp diff --git a/source/list.h b/source/list.h new file mode 100644 index 0000000..edcec33 --- /dev/null +++ b/source/list.h @@ -0,0 +1,75 @@ +/* $Id$ + +This file is part of libmspgltk +Copyright © 2007 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#ifndef MSP_GLTK_LIST_H_ +#define MSP_GLTK_LIST_H_ + +#include +#include "widget.h" + +namespace Msp { +namespace GLtk { + +class VSlider; + +/** +Shows a list of items, allowing the user to select one. A slider is included +to allow scrolling through a long list. +*/ +class List: public Widget +{ +public: + class Loader: public Widget::Loader + { + public: + Loader(List &); + private: + void item(const std::string &); + }; + +private: + std::vector items; + int sel_index; + unsigned first; + unsigned n_visible; + + const Part *items_part; + + VSlider *slider; + bool slider_active; + +public: + sigc::signal signal_item_selected; + + List(const Resources &); + void append(const std::string &); + void insert(unsigned, const std::string &); + void remove(unsigned); + void clear(); + + const std::string &get_selected() const; + int get_selected_index() const { return sel_index; } + + virtual void button_press(int, int, unsigned); + virtual void button_release(int, int, unsigned); + virtual void pointer_motion(int, int); + +private: + virtual const char *get_class() const { return "list"; } + virtual void render_special(const Part &) const; + + virtual void on_geometry_change(); + virtual void on_style_change(); + void reposition_slider(); + void recalculate_parameters(); + void slider_value_changed(double); +}; + +} // namespace GLtk +} // namespace Msp + +#endif diff --git a/source/logic.cpp b/source/logic.cpp new file mode 100644 index 0000000..40d06b2 --- /dev/null +++ b/source/logic.cpp @@ -0,0 +1,38 @@ +/* $Id$ + +This file is part of libmspgltk +Copyright © 2007 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include "logic.h" + +using namespace std; + +namespace Msp { +namespace GLtk { + +Logic::Loader::Loader(Logic &l, const map &w): + logic(l), + widgets(w) +{ + add("action", &Loader::action); +} + +void Logic::Loader::action(const string &wdg, const string &data) +{ + map::const_iterator i=widgets.find(wdg); + if(i==widgets.end()) + throw KeyError("Unknown widget", wdg); + + unsigned colon=data.find(':'); + WidgetAction act; + act.wdg=i->second; + act.type=data.substr(0, colon); + if(colon!=string::npos) + act.data=data.substr(colon+1); + logic.actions.push_back(act); +} + +} // namespace GLtk +} // namespace Msp diff --git a/source/logic.h b/source/logic.h new file mode 100644 index 0000000..689b39b --- /dev/null +++ b/source/logic.h @@ -0,0 +1,59 @@ +/* $Id$ + +This file is part of libmspgltk +Copyright © 2007 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#ifndef MSP_GLTK_LOGIC_H_ +#define MSP_GLTK_LOGIC_H_ + +#include +#include +#include +#include +#include +#include + +namespace Msp { +namespace GLtk { + +class Widget; + +/** +Stores use interface logic. This is stored as actions associated to widgets. +Each action has type and data. See also class Connector. +*/ +class Logic +{ +public: + class Loader: public DataFile::Loader + { + private: + Logic &logic; + const std::map &widgets; + + public: + Loader(Logic &, const std::map &); + private: + void action(const std::string &, const std::string &); + }; + + struct WidgetAction + { + Widget *wdg; + std::string type; + std::string data; + }; + +private: + std::list actions; + +public: + const std::list &get_actions() const { return actions; } +}; + +} // namespace GLtk +} // namespace Msp + +#endif diff --git a/source/panel.cpp b/source/panel.cpp index 91562ce..9373ff2 100644 --- a/source/panel.cpp +++ b/source/panel.cpp @@ -9,7 +9,9 @@ Distributed under the LGPL #include "button.h" #include "entry.h" #include "hslider.h" +#include "indicator.h" #include "label.h" +#include "list.h" #include "panel.h" #include "part.h" #include "vslider.h" @@ -53,15 +55,19 @@ void Panel::remove(Widget &wdg) void Panel::button_press(int x, int y, unsigned btn) { if(pointer_grab>0) - pointer_focus->button_press(x-geom.x, y-geom.y, btn); - else if(geom.is_inside(x, y)) + { + const Geometry &cgeom=pointer_focus->get_geometry(); + pointer_focus->button_press(x-cgeom.x, y-cgeom.y, btn); + } + else if(geom.is_inside_relative(x, y)) { if(Widget *wdg=get_child_at(x, y)) { set_pointer_focus(wdg, btn); set_input_focus(wdg); - wdg->button_press(x-geom.x, y-geom.y, btn); + const Geometry &cgeom=wdg->get_geometry(); + wdg->button_press(x-cgeom.x, y-cgeom.y, btn); } } } @@ -70,28 +76,38 @@ void Panel::button_release(int x, int y, unsigned btn) { if(pointer_grab>0) { - pointer_focus->button_release(x-geom.x, y-geom.y, btn); + const Geometry &cgeom=pointer_focus->get_geometry(); + pointer_focus->button_release(x-cgeom.x, y-cgeom.y, btn); if(btn==pointer_grab) set_pointer_focus(get_child_at(x, y), 0); } - else if(geom.is_inside(x, y)) + else if(geom.is_inside_relative(x, y)) { if(Widget *wdg=get_child_at(x, y)) - wdg->button_release(x-geom.x, y-geom.y, btn); + { + const Geometry &cgeom=wdg->get_geometry(); + wdg->button_release(x-cgeom.x, y-cgeom.y, btn); + } } } void Panel::pointer_motion(int x, int y) { if(pointer_grab>0) - pointer_focus->pointer_motion(x-geom.x, y-geom.y); - else if(geom.is_inside(x, y)) + { + const Geometry &cgeom=pointer_focus->get_geometry(); + pointer_focus->pointer_motion(x-cgeom.x, y-cgeom.y); + } + else if(geom.is_inside_relative(x, y)) { Widget *wdg=get_child_at(x, y); set_pointer_focus(wdg, 0); if(wdg) - wdg->pointer_motion(x-geom.x, y-geom.y); + { + const Geometry &cgeom=wdg->get_geometry(); + wdg->pointer_motion(x-cgeom.x, y-cgeom.y); + } } } @@ -169,7 +185,7 @@ void Panel::set_input_focus(Widget *wdg) Widget *Panel::get_child_at(int x, int y) { for(ChildSeq::reverse_iterator i=children.rbegin(); i!=children.rend(); ++i) - if((*i)->is_visible() && (*i)->get_geometry().is_inside(x-geom.x, y-geom.y)) + if((*i)->is_visible() && (*i)->get_geometry().is_inside(x, y)) return *i; return 0; @@ -184,7 +200,9 @@ Panel::Loader::Loader(Panel &p, map &m): add("button", &Loader::child