diff --git a/assets/gui/button.docx b/assets/gui/button.docx new file mode 100644 index 0000000000..0f3d20b3a5 --- /dev/null +++ b/assets/gui/button.docx @@ -0,0 +1,33 @@ +# struct subtexture +# one sprite, as part of a texture atlas. +# +# this struct stores information about positions and sizes +# of sprites included in the 'big texture'. +# int32_t,int32_t,int32_t,int32_t,int32_t,int32_t +# x,y,w,h,cx,cy + +# normal +0,0,3,3,0,0 +3,0,122,3,0,0 +125,0,3,3,0,0 + +0,3,3,58,0,0 +3,3,122,58,0,0 +125,3,3,58,0,0 + +0,61,3,3,0,0 +3,61,122,3,0,0 +125,61,3,3,0,0 + +# pressed +0,64,3,3,0,0 +3,64,122,3,0,0 +125,64,3,3,0,0 + +0,67,3,58,0,0 +3,67,122,58,0,0 +125,67,3,58,0,0 + +0,125,3,3,0,0 +3,125,122,3,0,0 +125,125,3,3,0,0 diff --git a/assets/gui/button.png b/assets/gui/button.png new file mode 100644 index 0000000000..742f8ec743 Binary files /dev/null and b/assets/gui/button.png differ diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 8f0b77b9a1..9a78260069 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory("console") add_subdirectory("coord") add_subdirectory("crossplatform") add_subdirectory("datastructure") +add_subdirectory("gui") add_subdirectory("log") add_subdirectory("job") add_subdirectory("keybinds") diff --git a/cpp/engine.cpp b/cpp/engine.cpp index 282117cfb7..b3168b07cb 100644 --- a/cpp/engine.cpp +++ b/cpp/engine.cpp @@ -374,20 +374,22 @@ int64_t Engine::lastframe_duration_nsec() { return this->fps_counter.nsec_lastframe; } -void Engine::render_text(coord::window position, size_t size, const char *format, ...) { +Font &Engine::get_font(size_t size) { auto it = this->fonts.find(size); if (it == this->fonts.end()) { throw util::Error(MSG(err) << "Unknown font size requested: " << size); } - Font *font = it->second.get(); + return *(it->second); +} +void Engine::render_text(coord::window position, size_t size, const char *format, ...) { va_list vl; va_start(vl, format); std::string buf = util::vsformat(format, vl); va_end(vl); - font->render_static(position.x, position.y, buf.c_str()); + get_font(size).render_static(position.x, position.y, buf.c_str()); } void Engine::move_phys_camera(float x, float y, float amount) { diff --git a/cpp/engine.h b/cpp/engine.h index 3de42a7822..217f98c14b 100644 --- a/cpp/engine.h +++ b/cpp/engine.h @@ -201,6 +201,11 @@ class Engine : public ResizeHandler { */ int64_t lastframe_duration_nsec(); + /** + * find a Font instance + */ + Font &get_font(size_t size); + /** * render text with the at a position with specified font size */ diff --git a/cpp/game_main.cpp b/cpp/game_main.cpp index 4280775c9f..a2e4b342ee 100644 --- a/cpp/game_main.cpp +++ b/cpp/game_main.cpp @@ -28,6 +28,15 @@ #include "util/timer.h" #include "util/externalprofiler.h" +#include "gui/container.h" +#include "gui/drawer.h" +#include "gui/label.h" +#include "gui/toplevel.h" +#include "gui/imagebutton.h" +#include "gui/imagecheckbox.h" +#include "gui/style.h" +#include "gui/formlayout.h" + namespace openage { /** @file @@ -354,6 +363,59 @@ GameMain::GameMain(Engine *engine) bind_player_switch(keybinds::action_t::SWITCH_TO_PLAYER_8, 8); engine->get_keybind_manager().register_context(&this->keybind_context); + + // create GUI + gui_top_level.reset(new gui::TopLevel); + gui_top_level->set_layout(std::make_unique()); + + gui::Style style{&assetmanager}; + + gui_loading_message = gui_top_level->create(); + gui_loading_message->set_text("Loading gamedata..."); + gui_loading_message->set_layout_data(gui::FormLayoutData::best_size(gui_loading_message) + .set_left(gui::FormAttachment::center()) + .set_top(gui::FormAttachment::center())); + + gui_framerate = gui_top_level->create(); + gui_framerate->set_text("⸘FPS here maybe‽"); + gui_framerate->set_layout_data(gui::FormLayoutData::best_size(gui_framerate) + .set_left(gui::FormAttachment::right().offset_by(-100)) + .set_bottom(gui::FormAttachment::bottom().offset_by(-30))); + + auto gui_idle_villager = gui_top_level->add_control(style.create_image_button("converted/Data/interfac.drs/50788.slp.png", 12)); + gui_idle_villager->set_layout_data(gui::FormLayoutData::best_size(gui_idle_villager) + .set_left(gui::FormAttachment::left().offset_by(5)) + .set_bottom(gui::FormAttachment::bottom().offset_by(-5))); + gui_idle_villager->on_click([]{ + log::log(MSG(info).fmt("Idle villager, anyone?")); + }); + +#if 0 + auto gui_flare = gui_top_level->add_control(style.create_image_button("converted/Data/interfac.drs/50788.slp.png", 2)); + gui_flare->set_right(gui::FormAttachment::left().offset_by(90)); // tmp + gui_flare->set_top(gui::FormAttachment::bottom().offset_by(-85)); // tmp + gui_flare->set_left(gui::FormAttachment::adjacent_to(gui_idle_villager).offset_by(10)); + gui_flare->set_bottom(gui::FormAttachment::bottom().offset_by(-50)); + gui_flare->on_click([]{ + log::log(MSG(info).fmt("Send a flare!")); + }); + + auto gui_quit = gui_top_level->add_control(style.create_button()); + gui_quit->set_left(gui::FormAttachment::right().offset_by(-42)); + gui_quit->set_right(gui::FormAttachment::right().offset_by(-10)); + gui_quit->set_top(gui::FormAttachment::top().offset_by(10)); + gui_quit->set_bottom(gui::FormAttachment::top().offset_by(40)); + gui_quit->get_label()->set_text("X"); + gui_quit->on_click([engine]{ + engine->stop(); + }); + + auto gui_palalol = gui_top_level->add_control(style.create_image_button("converted/Data/interfac.drs/50730.slp.png", 2)); + gui_palalol->set_left(gui::FormAttachment::left().offset_by(10)); + gui_palalol->set_right(gui::FormAttachment::left().offset_by(50)); + gui_palalol->set_top(gui::FormAttachment::center().offset_by(10)); + gui_palalol->set_bottom(gui::FormAttachment::center().offset_by(50)); +#endif } GameMain::~GameMain() { @@ -368,6 +430,11 @@ GameMain::~GameMain() { bool GameMain::on_input(SDL_Event *e) { Engine &engine = Engine::get(); + + // first, see if the gui wants to process the event + if (gui_top_level->process_event(e)) { + return true; + } switch (e->type) { @@ -651,6 +718,13 @@ bool GameMain::on_drawhud() { txt->sample(bpreview_pos.to_camhud(), engine->current_player); } } + + // draw the mighty GUI over everything + gui::Drawer drawer{e.engine_coord_data->window_size.x, e.engine_coord_data->window_size.y}; + gui_top_level->resize(e.engine_coord_data->window_size.x, e.engine_coord_data->window_size.y); // FIXME only resize when size actually changes + gui_top_level->update(0.0); + gui_top_level->draw(drawer); + return true; } diff --git a/cpp/game_main.h b/cpp/game_main.h index 0e4251303c..ff2706d6a3 100644 --- a/cpp/game_main.h +++ b/cpp/game_main.h @@ -22,6 +22,7 @@ #include "unit/unit_container.h" #include "util/externalprofiler.h" #include "gamedata/gamedata.gen.h" +#include "gui/forward.h" namespace openage { @@ -101,6 +102,10 @@ class GameMain : Command get_action(const coord::phys3 &pos) const; openage::Engine *engine; + + std::unique_ptr gui_top_level; + gui::Label *gui_loading_message; + gui::Label *gui_framerate; }; } //namespace openage diff --git a/cpp/gui/CMakeLists.txt b/cpp/gui/CMakeLists.txt new file mode 100644 index 0000000000..afe75b688c --- /dev/null +++ b/cpp/gui/CMakeLists.txt @@ -0,0 +1,15 @@ +add_sources(${PROJECT_NAME} + border.cpp + button.cpp + checkbox.cpp + container.cpp + control.cpp + drawer.cpp + formlayout.cpp + gridlayout.cpp + image.cpp + label.cpp + layout.cpp + style.cpp + toplevel.cpp +) diff --git a/cpp/gui/absolutelayout.h b/cpp/gui/absolutelayout.h new file mode 100644 index 0000000000..f2abec66fd --- /dev/null +++ b/cpp/gui/absolutelayout.h @@ -0,0 +1,31 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_ABSOLUTELAYOUT_H_ +#define OPENAGE_GUI_ABSOLUTELAYOUT_H_ + +#include "layout.h" + +namespace openage { +namespace gui { + +class AbsoluteLayoutData : public LayoutData { +public: + void set_position(int x, int y, int w, int h) { + position = {{x, x + w, y, y + h}}; + } +}; + +class AbsoluteLayout : public Layout { +protected: + virtual void layout(const std::vector> &controls, int container_w, int container_h) { + (void)controls; + (void)container_w; + (void)container_h; + } +}; + +} // namespace gui +} // namespace openage + +#endif + diff --git a/cpp/gui/alignment.h b/cpp/gui/alignment.h new file mode 100644 index 0000000000..97c4b322d4 --- /dev/null +++ b/cpp/gui/alignment.h @@ -0,0 +1,18 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_ALIGNMENT_H_ +#define OPENAGE_GUI_ALIGNMENT_H_ + +namespace openage { +namespace gui { + +enum class HorizontalAlignment { + LEFT, + CENTER, + RIGHT +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/border.cpp b/cpp/gui/border.cpp new file mode 100644 index 0000000000..931dc5c444 --- /dev/null +++ b/cpp/gui/border.cpp @@ -0,0 +1,59 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "border.h" + +#include "image.h" + +namespace openage { +namespace gui { + +Border::Border() { + for (auto &img : slices) { + img = create(); + } + contents = create(); +} + +void Border::set_texture(const Texture *texture, int base_subid) { + for (size_t i = 0; i < slices.size(); ++i) { + slices[i]->set_texture(texture, base_subid + i); + } + +#if 0 + slices[0]->set_left(FormAttachment::left()); + slices[3]->set_left(FormAttachment::left()); + slices[6]->set_left(FormAttachment::left()); + + slices[2]->set_right(FormAttachment::right()); + slices[5]->set_right(FormAttachment::right()); + slices[8]->set_right(FormAttachment::right()); + + slices[0]->set_top(FormAttachment::top()); + slices[1]->set_top(FormAttachment::top()); + slices[2]->set_top(FormAttachment::top()); + + slices[6]->set_bottom(FormAttachment::bottom()); + slices[7]->set_bottom(FormAttachment::bottom()); + slices[8]->set_bottom(FormAttachment::bottom()); + + slices[1]->set_left(FormAttachment::adjacent_to(slices[0])); + slices[4]->set_left(FormAttachment::adjacent_to(slices[3])); + slices[7]->set_left(FormAttachment::adjacent_to(slices[6])); + + slices[1]->set_right(FormAttachment::adjacent_to(slices[2])); + slices[4]->set_right(FormAttachment::adjacent_to(slices[5])); + slices[7]->set_right(FormAttachment::adjacent_to(slices[8])); + + slices[3]->set_top(FormAttachment::adjacent_to(slices[0])); + slices[4]->set_top(FormAttachment::adjacent_to(slices[1])); + slices[5]->set_top(FormAttachment::adjacent_to(slices[2])); + + slices[3]->set_bottom(FormAttachment::adjacent_to(slices[6])); + slices[4]->set_bottom(FormAttachment::adjacent_to(slices[7])); + slices[5]->set_bottom(FormAttachment::adjacent_to(slices[8])); +#endif +} + +} // namespace gui +} // namespace openage + diff --git a/cpp/gui/border.h b/cpp/gui/border.h new file mode 100644 index 0000000000..b9b187ecc3 --- /dev/null +++ b/cpp/gui/border.h @@ -0,0 +1,36 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_BORDER_H_ +#define OPENAGE_GUI_BORDER_H_ + +#include "../texture.h" +#include "container.h" +#include "forward.h" + +#include + +namespace openage { +namespace gui { + +class Border : public ContainerBase { +public: + Border(); + + void set_texture(const Texture *texture, int base_subid); + + Container *get_contents() { + return contents; + } + +protected: + std::array slices; + Container *contents; +}; + +} // namespace gui +} // namespace openage + +#endif + + + diff --git a/cpp/gui/button.cpp b/cpp/gui/button.cpp new file mode 100644 index 0000000000..7094ee8bf7 --- /dev/null +++ b/cpp/gui/button.cpp @@ -0,0 +1,57 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "button.h" + +#include + +namespace openage { +namespace gui { + +bool Button::mouse_moved_bg(int x, int y) { + if (state == State::NORMAL) { + assert(contains(x, y)); + set_state(State::HOVERED); + } + else if (state == State::DOWN) { + if (!contains(x, y)) set_state(State::DOWN_OUT); + } + else if (state == State::DOWN_OUT) { + if (contains(x, y)) set_state(State::DOWN); + } + return true; +} + +void Button::mouse_left_bg() { + set_state(State::NORMAL); +} + +bool Button::mouse_pressed_bg(std::uint8_t button) { + if (button == SDL_BUTTON_LEFT) { + set_state(State::DOWN); + return true; + } + return false; +} + +void Button::mouse_released_bg(std::uint8_t button) { + if (button == SDL_BUTTON_LEFT) { + //if (state == State::NORMAL) return; // why would this happen? + + if (state == State::DOWN) { + set_state(State::HOVERED); + sig_click.emit(); + } + else { + assert(state == State::DOWN_OUT); + set_state(State::NORMAL); + } + } +} + +void Button::set_state(State new_state) { + state = new_state; + sig_state_change.emit(new_state); +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/button.h b/cpp/gui/button.h new file mode 100644 index 0000000000..d49489433d --- /dev/null +++ b/cpp/gui/button.h @@ -0,0 +1,62 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_BUTTON_H_ +#define OPENAGE_GUI_BUTTON_H_ + +#include "container.h" +#include "forward.h" +#include "signal.h" + +namespace openage { +namespace gui { + +class Button : public Container { +public: + enum class State { + NORMAL, HOVERED, DOWN, DOWN_OUT + }; + + virtual void mouse_left_bg() override; + virtual bool mouse_moved_bg(int x, int y) override; + virtual bool mouse_pressed_bg(std::uint8_t button) override; + virtual void mouse_released_bg(std::uint8_t button) override; + + void on_click(Signal<>::function_t &&handler) { + sig_click.connect(std::move(handler)); + } + + void on_state_change(Signal::function_t &&handler) { + handler(get_state()); + sig_state_change.connect(std::move(handler)); + } + + State get_state() const { + return state; + } +protected: + virtual void set_state(State new_state); + + State state = State::NORMAL; + + Signal<> sig_click; + Signal sig_state_change; +}; + +class LabelButton : public Button { +public: + Label *get_label() { + return label; + } + + void set_label(Label *label) { + this->label = label; + } + +protected: + Label *label = nullptr; +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/checkbox.cpp b/cpp/gui/checkbox.cpp new file mode 100644 index 0000000000..2abb53d961 --- /dev/null +++ b/cpp/gui/checkbox.cpp @@ -0,0 +1,21 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "checkbox.h" + +namespace openage { +namespace gui { + +CheckBox::CheckBox() { + on_click([this]{ + set_checked(!checked); // toggle state + if (on_toggle) on_toggle(checked); + }); +} + +void CheckBox::set_checked(bool new_checked) { + checked = new_checked; +} + +} // namespace gui +} // namespace openage + diff --git a/cpp/gui/checkbox.h b/cpp/gui/checkbox.h new file mode 100644 index 0000000000..558de55f42 --- /dev/null +++ b/cpp/gui/checkbox.h @@ -0,0 +1,31 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_CHECKBOX_H_ +#define OPENAGE_GUI_CHECKBOX_H_ + +#include "button.h" + +namespace openage { +namespace gui { + +class CheckBox : public Button { +public: + CheckBox(); + + bool is_checked() { + return checked; + } + + std::function on_toggle; // should be some kind of signal + +protected: + virtual void set_checked(bool new_checked); + + bool checked = false; +}; + +} // namespace gui +} // namespace openage + +#endif + diff --git a/cpp/gui/container.cpp b/cpp/gui/container.cpp new file mode 100644 index 0000000000..76d60ebe63 --- /dev/null +++ b/cpp/gui/container.cpp @@ -0,0 +1,171 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "container.h" +#include "drawer.h" +#include "layout.h" + +namespace openage { +namespace gui { + +ContainerBase::ContainerBase() = default; + +ContainerBase::~ContainerBase() = default; + +void ContainerBase::update(seconds_t t) { + if (needs_layout) { + needs_layout = false; + auto w = width(); + auto h = height(); + layout->layout(*this); + if (mt.has_mouse()) { + mouse_moved(mt.x(), mt.y()); + } + } + + for (auto &c : controls) { + c->update(t); + } +} + +void ContainerBase::draw(const Drawer &drawer) const { + Drawer dr = drawer; + dr.translate(left(), top()); + for (auto &c : controls) { + c->draw(dr); + } +} + +bool ContainerBase::contains(int x, int y) const { + if (!Control::contains(x, y)) return false; + x -= left(); y -= top(); + for (auto &c : controls) { + if (c->contains(x, y)) { + return true; + } + } + return false; +} + +std::tuple ContainerBase::get_best_size() const { + return layout->get_best_size(controls); +} + +void ContainerBase::mouse_left() { + assert(mouse_capture_count == 0); + mt.mouse_left(); + if (control_under_mouse) { + control_under_mouse->mouse_left(); + control_under_mouse = nullptr; + } + mouse_left_bg(); +} + +bool ContainerBase::mouse_moved(int x, int y) { + mt.mouse_moved(x, y); + x -= left(); y -= top(); + if (mouse_capture_count > 0) { + if (control_under_mouse) { + control_under_mouse->mouse_moved(x, y); + } + else { + mouse_moved_bg(mt.x(), mt.y()); + } + return true; // always consume event + } + + if (control_under_mouse && !control_under_mouse->contains(x, y)) { + control_under_mouse->mouse_left(); + control_under_mouse = nullptr; + } + + if (!control_under_mouse) { + control_under_mouse = control_at(x, y); + } + + if (control_under_mouse && control_under_mouse->mouse_moved(x, y)) { + mouse_left_bg(); + return true; + } + + return mouse_moved_bg(mt.x(), mt.y()); +} + +bool ContainerBase::mouse_pressed(std::uint8_t button) { + if (mouse_capture_count > 0) { + if (control_under_mouse) { + control_under_mouse->mouse_pressed(button); + } + else { + mouse_pressed_bg(button); + } + mouse_capture_count += 1; + return true; // always consume event + } + if (control_under_mouse) { + if (control_under_mouse->mouse_pressed(button)) { + mouse_capture_count += 1; + return true; + } + control_under_mouse->mouse_left(); + control_under_mouse = nullptr; + } + + if (mouse_pressed_bg(button)) { + mouse_capture_count += 1; + return true; + } + return false; +} + +void ContainerBase::mouse_released(std::uint8_t button) { + assert(mouse_capture_count > 0); + if (control_under_mouse) { + control_under_mouse->mouse_released(button); + } + else { + mouse_released_bg(button); + } + + mouse_capture_count -= 1; + if (mouse_capture_count == 0) { + if (!contains(mt.x(), mt.y())) { + mouse_left(); + return; + } + + mouse_moved(mt.x(), mt.y()); + } +} + +bool ContainerBase::mouse_scrolled(int relX, int relY) { + if (mouse_capture_count > 0) { + if (control_under_mouse) { + control_under_mouse->mouse_scrolled(relX, relY); + } + else { + mouse_scrolled_bg(relX, relY); + } + return true; // always consume event + } + if (control_under_mouse && control_under_mouse->mouse_scrolled(relX, relY)) { + return true; + } + return mouse_scrolled_bg(relX, relY); +} + +Control *ContainerBase::control_at(int x, int y) const { + for (auto &c : controls) { + if (c->contains(x, y)) { + return c.get(); + } + } + return nullptr; +} + +void ContainerBase::set_layout(std::unique_ptr &&layout) { + this->layout = std::move(layout); + needs_layout = true; +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/container.h b/cpp/gui/container.h new file mode 100644 index 0000000000..7a54ab551f --- /dev/null +++ b/cpp/gui/container.h @@ -0,0 +1,113 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_CONTAINER_H_ +#define OPENAGE_GUI_CONTAINER_H_ + +#include "control.h" +#include "forward.h" + +#include + +namespace openage { +namespace gui { + +class ContainerBase : public Control { + friend class Layout; +public: + ContainerBase(); + virtual ~ContainerBase(); + + virtual void update(seconds_t t) override; + virtual void draw(const Drawer &drawer) const override; + virtual bool contains(int x, int y) const override; + + virtual std::tuple get_best_size() const override; + + virtual void mouse_left() override; + virtual bool mouse_moved(int x, int y) override; + virtual bool mouse_pressed(std::uint8_t button) override; + virtual void mouse_released(std::uint8_t button) override; + virtual bool mouse_scrolled(int relX, int relY) override; + + virtual void mouse_left_bg() {} + virtual bool mouse_moved_bg(int /*x*/, int /*y*/) { return false; } + virtual bool mouse_pressed_bg(std::uint8_t /*button*/) { return false; } + virtual void mouse_released_bg(std::uint8_t /*button*/) { } + virtual bool mouse_scrolled_bg(int /*relX*/, int /*relY*/) { return false; } + +protected: + Control *control_at(int x, int y) const; + + void set_layout(std::unique_ptr &&layout); + + template + ControlType *create(Args&&... args) { + auto retVal = new ControlType{std::forward(args)...}; + add_control(std::unique_ptr{retVal}); + return retVal; + } + + template + std::unique_ptr remove_control(ControlType *control) { + if (control_under_mouse == control) { + control_under_mouse = nullptr; + } + auto it = std::find_if(controls.begin(), controls.end(), + [control] (const std::unique_ptr &ptr) -> bool { + return ptr.get() == control; + }); + it->release(); // returns control + controls.erase(it); + needs_layout = true; + return std::unique_ptr(control); + } + + template + ControlType *add_control(std::unique_ptr &&control) { + ControlType *retVal = control.get(); + do_add_control(std::move(control)); + return retVal; + } + + void do_add_control(std::unique_ptr &&control) { + //control->invalidate_layout(); TODO + controls.push_back(std::move(control)); + needs_layout = true; + } + + std::vector> clear() { + control_under_mouse = nullptr; + auto retVal(std::move(controls)); + retVal.clear(); + needs_layout = false; + return retVal; + } + + void set_controls(std::vector> &&controls) { + this->controls = std::move(controls); + needs_layout = true; + } + + const std::vector> &get_controls() const { return controls; } + + MouseTracker mt; + std::vector> controls; + std::unique_ptr layout; + Control *control_under_mouse = nullptr; + int mouse_capture_count = 0; + + bool needs_layout = false; +}; + +class Container : public ContainerBase { +public: + using ContainerBase::set_layout; + using ContainerBase::create; + using ContainerBase::add_control; + using ContainerBase::remove_control; +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/control.cpp b/cpp/gui/control.cpp new file mode 100644 index 0000000000..30c7372836 --- /dev/null +++ b/cpp/gui/control.cpp @@ -0,0 +1,21 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "control.h" + +#include "layout.h" + +namespace openage { +namespace gui { + +constexpr int MouseTracker::MOUSE_OUT; + +Control::Control() = default; + +Control::~Control() = default; + +void Control::set_layout_data(std::unique_ptr &&layout_data) { + this->layout_data = std::move(layout_data); +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/control.h b/cpp/gui/control.h new file mode 100644 index 0000000000..5fa016f821 --- /dev/null +++ b/cpp/gui/control.h @@ -0,0 +1,90 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_CONTROL_H_ +#define OPENAGE_GUI_CONTROL_H_ + +#include "forward.h" +#include "layout.h" + +#include +#include +#include +#include +#include + +namespace openage { +namespace gui { + +using seconds_t = double; + +class Control { + friend class Layout; +public: + Control(); + Control(const Control &other) = delete; + Control(Control &&other) = delete; + + void operator =(const Control &other) = delete; + void operator =(Control &&other) = delete; + + virtual ~Control(); + + template + void set_layout_data(const T &layout_data) { + set_layout_data(std::unique_ptr{new T{layout_data}}); + } + + void set_layout_data(std::unique_ptr &&layout_data); + + virtual bool contains(int x, int y) const { + if (x < left()) return false; + if (x >= right()) return false; + if (y < top()) return false; + if (y >= bottom()) return false; + return true; + } + + virtual void update(seconds_t /*t*/) {} + + virtual void draw(const Drawer &drawer) const = 0; + + int left() const { return layout_data->left(); } + int right() const { return layout_data->right(); } + int top() const { return layout_data->top(); } + int bottom() const { return layout_data->bottom(); } + int width() const { return right() - left(); } + int height() const { return bottom() - top(); } + + virtual std::tuple get_best_size() const = 0; + + virtual void mouse_left() { } + virtual bool mouse_moved(int /*x*/, int /*y*/) { return false; } + virtual bool mouse_pressed(std::uint8_t /*button*/) { return false; } + virtual void mouse_released(std::uint8_t /*button*/) { } + virtual bool mouse_scrolled(int /*relX*/, int /*relY*/) { return false; } + +protected: + std::unique_ptr layout_data; +}; + +class MouseTracker { +public: + void mouse_moved(int x, int y) { mX = x; mY = y; } + + void mouse_left() { mX = MOUSE_OUT; } + + bool has_mouse() const { return mX != MOUSE_OUT; } + + int x() const { assert(has_mouse()); return mX; } + + int y() const { assert(has_mouse()); return mY; } + +private: + static constexpr int MOUSE_OUT = std::numeric_limits::min(); + int mX = MOUSE_OUT, mY; +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/drawer.cpp b/cpp/gui/drawer.cpp new file mode 100644 index 0000000000..d7fb84fc3b --- /dev/null +++ b/cpp/gui/drawer.cpp @@ -0,0 +1,50 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "drawer.h" + +#include "../engine.h" + +namespace openage { +namespace gui { + +Drawer::Drawer(int w, int h) : + scale_x{1.0f}, scale_y{-1.0f}, + translate_x{0.0f}, translate_y{static_cast(-h)} { + (void)w; +} + +void Drawer::draw_texture(const Texture *texture, int subid, float gui_x, float gui_y) const { + int width, height; + texture->get_subtexture_size(subid, &width, &height); + + int left, right, bottom, top; + std::tie(left, top) = to_screen_coord(gui_x, gui_y); + std::tie(right, bottom) = to_screen_coord(gui_x + width, gui_y + height); + + int orig_left = left; + int orig_top = top; + + std::tie(left, top) = clip_coord(std::make_tuple(left, top)); + std::tie(right, bottom) = clip_coord(std::make_tuple(right, bottom)); + + width = right - left; + if (width <= 0) return; + + height = top - bottom; + if (height <= 0) return; + + texture->draw_clipped(left, bottom, + left - orig_left, top - orig_top, + width, height, + subid); +} + +void Drawer::draw_text(const std::string &text, int size, float gui_x, float gui_y) const { + // ignores clipping for now + int left, top; + std::tie(left, top) = to_screen_coord(gui_x, gui_y); + Engine::get().render_text({left, top - size + 2}, size, "%s", text.c_str()); +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/drawer.h b/cpp/gui/drawer.h new file mode 100644 index 0000000000..c092b133e9 --- /dev/null +++ b/cpp/gui/drawer.h @@ -0,0 +1,79 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_DRAWER_H_ +#define OPENAGE_GUI_DRAWER_H_ + +#include "../texture.h" + +#include +#include +#include +#include + +namespace openage { +namespace gui { + +class Drawer { +public: + Drawer(int w, int h); + + void scale(float scale) { + this->scale(scale, scale); + } + + void scale(float scale_x, float scale_y) { + this->scale_x *= scale_x; + this->scale_y *= scale_y; + this->translate_x /= scale_x; + this->translate_y /= scale_y; + } + + void translate(float translate_x, float translate_y) { + this->translate_x += translate_x; + this->translate_y += translate_y; + } + + void clip(float x, float y, float w, float h) { + std::tie(clipL, clipT) = clip_coord(to_screen_coord(x, y)); + std::tie(clipR, clipB) = clip_coord(to_screen_coord(x + w, y + h)); + } + + void draw_texture(const Texture *texture, int subid, float gui_x, float gui_y) const; + + void draw_text(const std::string &text, int size, float gui_x, float gui_y) const; + +protected: + std::tuple to_screen_coord(float gui_x, float gui_y) const { + int x = std::lround((gui_x + translate_x) * scale_x); + int y = std::lround((gui_y + translate_y) * scale_y); + return std::make_tuple(x, y); + } + + std::tuple clip_coord(std::tuple hud_coord) const { + auto &x = std::get<0>(hud_coord); + auto &y = std::get<1>(hud_coord); + + if (x < clipL) x = clipL; + else if (x > clipR) x = clipR; + + if (y < clipB) y = clipB; + else if (y > clipT) y = clipT; + + return hud_coord; + } + +private: + float scale_x, scale_y; + float translate_x, translate_y; + + // uses the screen coordinate system + int clipL = std::numeric_limits::lowest(); + int clipR = std::numeric_limits::max(); + int clipB = std::numeric_limits::lowest(); + int clipT = std::numeric_limits::max(); +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/formlayout.cpp b/cpp/gui/formlayout.cpp new file mode 100644 index 0000000000..34edb216d0 --- /dev/null +++ b/cpp/gui/formlayout.cpp @@ -0,0 +1,92 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "formlayout.h" +#include "container.h" + +#include + +namespace openage { +namespace gui { + +FormLayoutData FormLayoutData::best_size(const Control *control) { + int best_w, best_h; + std::tie(best_w, best_h) = control->get_best_size(); + + return FormLayoutData() + .set_left(FormAttachment::adjacent_to(control).offset_by(-best_w)) + .set_right(FormAttachment::adjacent_to(control).offset_by(best_w)) + .set_top(FormAttachment::adjacent_to(control).offset_by(-best_h)) + .set_bottom(FormAttachment::adjacent_to(control).offset_by(best_h)); +} + +void FormLayout::layout(const std::vector> &controls, int container_w, int container_h) { + if (last_width == container_w && last_height == container_h) { + return; + } + + last_width = container_w; + last_height = container_h; + + for (auto &control : controls) { + get_layout_data(control.get())->invalidate(); + } + for (auto &control : controls) { + layout(control.get(), container_w, container_h); + } +} + +void FormLayout::layout(const Control *control, int container_w, int container_h) { + FormLayoutData *data = dynamic_cast(get_layout_data(control)); + assert(data->position[3] != FormLayoutData::EVALUATING_POS); + if (data->position[3] == FormLayoutData::INVALID_POS) { + data->position[3] = FormLayoutData::EVALUATING_POS; + } + else { + return; + } + + std::array naturalPos = {{container_w, container_h}}; + for (size_t i = 0; i < 4; ++i) { + auto attach = data->attachments[i]; + + if (attach.control == control) { // control attached to itself + assert(attach.alignment == FormAttachment::Alignment::ADJACENT); + auto &other = data->attachments[i ^ 1]; + attach.alignment = FormAttachment::Alignment::ALIGNED; + attach.control = other.control; + attach.offset += other.offset; + attach.numerator = attach.numerator * other.denominator + other.numerator * attach.denominator; + attach.denominator *= other.denominator; + } + + int pos = naturalPos[i / 2] * attach.numerator / attach.denominator + attach.offset; + if (attach.control) { + layout(attach.control, container_w, container_h); // has to be a sibling! + FormLayoutData *other_data = dynamic_cast(get_layout_data(attach.control)); + switch(attach.alignment) { + case FormAttachment::Alignment::ADJACENT: + pos += other_data->position[i ^ 1]; + break; + case FormAttachment::Alignment::ALIGNED: + pos += other_data->position[i]; + break; + case FormAttachment::Alignment::CENTER: + pos += (other_data->position[i] + other_data->position[i ^ 1]) / 2; + } + } + data->position[i] = pos; + } +} + +std::tuple FormLayout::get_best_size(const std::vector> &controls) { + std::tuple retVal{0, 0}; + for(auto &ctrl : controls) { + auto s = ctrl->get_best_size(); + std::get<0>(retVal) = std::max(std::get<0>(retVal), std::get<0>(s)); + std::get<1>(retVal) = std::max(std::get<1>(retVal), std::get<1>(s)); + } + return retVal; +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/formlayout.h b/cpp/gui/formlayout.h new file mode 100644 index 0000000000..c1821a8458 --- /dev/null +++ b/cpp/gui/formlayout.h @@ -0,0 +1,140 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_FORMLAYOUT_H_ +#define OPENAGE_GUI_FORMLAYOUT_H_ + +#include "forward.h" +#include "layout.h" + +#include + +namespace openage { +namespace gui { + +/// Modelled after SWT. +class FormAttachment { + friend class FormLayout; +public: + static constexpr FormAttachment adjacent_to(const Control *control) { + return {control, Alignment::ADJACENT, 0, 1}; + } + + static constexpr FormAttachment aligned_with(const Control *control) { + return {control, Alignment::ALIGNED, 0, 1}; + } + + static constexpr FormAttachment centered_on(const Control *control) { + return {control, Alignment::CENTER, 0, 1}; + } + + static constexpr FormAttachment fraction_parent(int numerator, int denominator) { + return {nullptr, Alignment::ADJACENT, numerator, denominator}; + } + + static constexpr FormAttachment percent_parent(int percent) { + return fraction_parent(percent, 100); + } + + static constexpr FormAttachment left() { + return fraction_parent(0, 1); + } + + static constexpr FormAttachment center() { + return fraction_parent(1, 2); + } + + static constexpr FormAttachment right() { + return fraction_parent(1, 1); + } + + static constexpr FormAttachment top() { + return left(); + } + + static constexpr FormAttachment bottom() { + return right(); + } + + FormAttachment offset_by(int offset) const { + FormAttachment retVal = *this; + retVal.offset += offset; + return retVal; + } + +protected: + enum class Alignment { + ADJACENT, // SWT.DEFAULT + ALIGNED, + CENTER + }; + + constexpr FormAttachment(const Control *control, Alignment alignment, int numerator, int denominator) : + control(control), + alignment(alignment), + numerator(numerator), + denominator(denominator), + offset(0) { + // empty + } + + const Control *control; + Alignment alignment; + int numerator; + int denominator; + int offset; +}; + +class FormLayoutData : public LayoutData { + friend class FormLayout; +public: + static FormLayoutData fill() { + return FormLayoutData{}; + } + + static FormLayoutData best_size(const Control *control); + + FormLayoutData set_left(const FormAttachment &left) { + attachments[0] = left; + return *this; + } + + FormLayoutData set_right(const FormAttachment &right) { + attachments[1] = right; + return *this; + } + + FormLayoutData set_top(const FormAttachment &top) { + attachments[2] = top; + return *this; + } + + FormLayoutData set_bottom(const FormAttachment &bottom) { + attachments[3] = bottom; + return *this; + } + +protected: + FormLayoutData() = default; + + // left, right, top, bottom (default: fill container) + std::array attachments = {{ + FormAttachment::left(), + FormAttachment::right(), + FormAttachment::top(), + FormAttachment::bottom() + }}; +}; + +class FormLayout : public Layout { +public: + virtual void layout(const std::vector> &controls, int container_w, int container_h) override; + + virtual std::tuple get_best_size(const std::vector> &controls) override; +protected: + virtual void layout(const Control *control, int container_w, int container_h); +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/forward.h b/cpp/gui/forward.h new file mode 100644 index 0000000000..8bea62220f --- /dev/null +++ b/cpp/gui/forward.h @@ -0,0 +1,33 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_FORWARD_H_ +#define OPENAGE_GUI_FORWARD_H_ + +/** + * Forward declarations for all GUI controls. + * Typically, you only need to include this file in your header. + */ + +namespace openage { +namespace gui { + +class Border; +class Button; +class CheckBox; +class Container; +class ContainerBase; +class Control; +class Drawer; +class FormLayout; +class Image; +class Label; +class LabelButton; +class Layout; +class LayoutData; +class TopLevel; + +} // namespace gui +} // namespace openage + +#endif + diff --git a/cpp/gui/gridlayout.cpp b/cpp/gui/gridlayout.cpp new file mode 100644 index 0000000000..6e56cbf28d --- /dev/null +++ b/cpp/gui/gridlayout.cpp @@ -0,0 +1,66 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "gridlayout.h" +#include "container.h" + +#include + +namespace openage { +namespace gui { + +void GridLayout::layout(const std::vector> &controls, int container_w, int container_h) { +} + +std::tuple GridLayout::get_best_size(const std::vector> &controls) { + int widest_row = 0; + int highest_col = 0; + int rows = 0; + int cols = 0; + + for (auto &ctrl : controls) { + auto *data = dynamic_cast(get_layout_data(ctrl.get())); + + int row = data->y + data->h; + if (row > rows) { + rows = row; + } + + int col = data->x + data->w; + if (col > cols) { + cols = col; + } + } + + for (int row = 0; row < rows; ++row) { + int row_w = 0; + for (auto &ctrl : controls) { + auto *data = dynamic_cast(get_layout_data(ctrl.get())); + if(data->y > row) continue; + if(data->y + data->h <= row) continue; + row_w += std::get<0>(ctrl->get_best_size()); + } + + if(row_w > widest_row) { + widest_row = row_w; + } + } + + for (int col = 0; col < cols; ++col) { + int col_h = 0; + for (auto &ctrl : controls) { + auto *data = dynamic_cast(get_layout_data(ctrl.get())); + if(data->x > col) continue; + if(data->x + data->w <= col) continue; + col_h += std::get<1>(ctrl->get_best_size()); + } + + if(col_h > highest_col) { + highest_col = col_h; + } + } + + return std::make_tuple(widest_row, highest_col); +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/gridlayout.h b/cpp/gui/gridlayout.h new file mode 100644 index 0000000000..a23e39e1c4 --- /dev/null +++ b/cpp/gui/gridlayout.h @@ -0,0 +1,64 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_GRIDLAYOUT_H_ +#define OPENAGE_GUI_GRIDLAYOUT_H_ + +#include "forward.h" +#include "layout.h" + +#include + +namespace openage { +namespace gui { + +class GridLayoutData : public LayoutData { + friend class GridLayout; +public: + static GridLayoutData left() { + return GridLayoutData{0, 1, 1, 1}; + } + + static GridLayoutData right() { + return GridLayoutData{0, 1, 1, 1}; + } + + static GridLayoutData center() { + return GridLayoutData{1, 1, 1, 1}; + } + + static GridLayoutData at(int x, int y) { + return GridLayoutData{x, y, 1, 1}; + } + + GridLayoutData span(int w, int h) { + GridLayoutData retVal = *this; + retVal.w = w; + retVal.h = h; + return retVal; + } + +protected: + GridLayoutData() = default; + + GridLayoutData(int x, int y, int w, int h) : + x(x), y(y), w(w), h(h) { + // empty + } + + int x, y, w, h; +}; + +class GridLayout : public Layout { +public: + virtual void layout(const std::vector> &controls, int container_w, int container_h) override; + + virtual std::tuple get_best_size(const std::vector> &controls) override; +protected: +}; + +} // namespace gui +} // namespace openage + +#endif + + diff --git a/cpp/gui/image.cpp b/cpp/gui/image.cpp new file mode 100644 index 0000000000..1b3d20d075 --- /dev/null +++ b/cpp/gui/image.cpp @@ -0,0 +1,32 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "image.h" + +#include "drawer.h" + +namespace openage { +namespace gui { + +void Image::set_texture(const Texture *texture, int subid) { + this->texture = texture; + this->subid = subid; +} + +void Image::draw(const Drawer &drawer) const { + if (!texture) return; + + Drawer dr{drawer}; + dr.clip(left(), top(), width(), height()); + + int subw, subh; + texture->get_subtexture_size(subid, &subw, &subh); + + for (auto x = left(); x < right(); x += subw) { + for (auto y = top(); y < bottom(); y += subh) { + dr.draw_texture(texture, subid, x, y); + } + } +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/image.h b/cpp/gui/image.h new file mode 100644 index 0000000000..82d3ed1131 --- /dev/null +++ b/cpp/gui/image.h @@ -0,0 +1,33 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_IMAGE_H_ +#define OPENAGE_GUI_IMAGE_H_ + +#include "control.h" + +#include "../texture.h" + +namespace openage { +namespace gui { + +class Image : public Control { +public: + void set_texture(const Texture *texture, int subid=0); + + void draw(const Drawer &drawer) const override; + + virtual std::tuple get_best_size() const override { + std::tuple retVal; + texture->get_subtexture_size(subid, &std::get<0>(retVal), &std::get<1>(retVal)); + return retVal; + } + +protected: + const Texture *texture; + int subid; +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/imagebutton.cpp b/cpp/gui/imagebutton.cpp new file mode 100644 index 0000000000..a374843dd2 --- /dev/null +++ b/cpp/gui/imagebutton.cpp @@ -0,0 +1,45 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "imagebutton.h" + +#include "image.h" + +namespace openage { +namespace gui { + +ImageButton::ImageButton() { + image = create(); +} + +void ImageButton::auto_size() { + if (!normal_texture) return; + + int subw, subh; + normal_texture->get_subtexture_size(normal_subid, &subw, &subh); + + set_left(FormAttachment::adjacent_to(this).offset_by(-subw)); + set_right(FormAttachment::adjacent_to(this).offset_by(subw)); + set_top(FormAttachment::adjacent_to(this).offset_by(-subh)); + set_bottom(FormAttachment::adjacent_to(this).offset_by(subh)); +} + +void ImageButton::set_state(State new_state) { + Button::set_state(new_state); + + update_texture(); +} + +void ImageButton::update_texture() { + if (state == State::HOVERED && hovered_texture) { + image->set_texture(hovered_texture, hovered_subid); + } + else if (state == State::DOWN && pressed_texture) { + image->set_texture(pressed_texture, pressed_subid); + } + else { + image->set_texture(normal_texture, normal_subid); // even if it's nullptr + } +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/imagebutton.h b/cpp/gui/imagebutton.h new file mode 100644 index 0000000000..34271428d7 --- /dev/null +++ b/cpp/gui/imagebutton.h @@ -0,0 +1,66 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_IMAGEBUTTON_H_ +#define OPENAGE_GUI_IMAGEBUTTON_H_ + +#include "../texture.h" +#include "button.h" +#include "forward.h" + +namespace openage { +namespace gui { + +class ImageButton : public Button { +public: + ImageButton(); + + void auto_size(); + + void set_textures(const Texture *texture, int normal_subid, int pressed_subid = -1, int hovered_subid = -1) { + this->normal_texture = texture; + this->normal_subid = normal_subid; + this->pressed_texture = (pressed_subid == -1) ? nullptr : texture; + this->pressed_subid = pressed_subid; + this->hovered_texture = (hovered_subid == -1) ? nullptr : texture; + this->hovered_subid = hovered_subid; + update_texture(); + } + + void set_normal_texture(const Texture *normal_texture, int normal_subid) { + this->normal_texture = normal_texture; + this->normal_subid = normal_subid; + update_texture(); + } + + void set_pressed_texture(const Texture *pressed_texture, int pressed_subid) { + this->pressed_texture = pressed_texture; + this->pressed_subid = pressed_subid; + update_texture(); + } + + void set_hovered_texture(const Texture *hovered_texture, int hovered_subid) { + this->hovered_texture = hovered_texture; + this->hovered_subid = hovered_subid; + update_texture(); + } + +protected: + virtual void set_state(State new_state) override; + + void update_texture(); + + const Texture *normal_texture = nullptr; + int normal_subid = 0; + const Texture *pressed_texture = nullptr; + int pressed_subid; + const Texture *hovered_texture = nullptr; + int hovered_subid; + + Image *image; +}; + +} // namespace gui +} // namespace openage + +#endif + diff --git a/cpp/gui/imagecheckbox.cpp b/cpp/gui/imagecheckbox.cpp new file mode 100644 index 0000000000..ad013d122e --- /dev/null +++ b/cpp/gui/imagecheckbox.cpp @@ -0,0 +1,41 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "imagecheckbox.h" + +#include "image.h" + +namespace openage { +namespace gui { + +ImageCheckBox::ImageCheckBox() { + image = create(); +} + +void ImageCheckBox::auto_size() { + if (!normal_texture) return; + + int subw, subh; + normal_texture->get_subtexture_size(normal_subid, &subw, &subh); + + set_left(FormAttachment::adjacent_to(this).offset_by(-subw)); + set_right(FormAttachment::adjacent_to(this).offset_by(subw)); + set_top(FormAttachment::adjacent_to(this).offset_by(-subh)); + set_bottom(FormAttachment::adjacent_to(this).offset_by(subh)); +} + +void ImageCheckBox::set_checked(bool new_checked) { + CheckBox::set_checked(new_checked); + + update_texture(); +} + +void ImageCheckBox::update_texture() { + if (checked) { + image->set_texture(normal_texture, checked_subid); + } else { + image->set_texture(normal_texture, normal_subid); + } +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/imagecheckbox.h b/cpp/gui/imagecheckbox.h new file mode 100644 index 0000000000..ce08272d96 --- /dev/null +++ b/cpp/gui/imagecheckbox.h @@ -0,0 +1,43 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_IMAGECHECKBOX_H_ +#define OPENAGE_GUI_IMAGECHECKBOX_H_ + +#include "../texture.h" +#include "checkbox.h" +#include "forward.h" + +namespace openage { +namespace gui { + +class ImageCheckBox : public CheckBox { +public: + ImageCheckBox(); + + void auto_size(); + + void set_textures(const Texture *texture, int normal_subid, int checked_subid = -1) { + this->normal_texture = texture; + this->normal_subid = normal_subid; + this->checked_subid = checked_subid; + update_texture(); + } + +protected: + virtual void set_checked(bool new_checked) override; + + void update_texture(); + + const Texture *normal_texture = nullptr; + int normal_subid = 0; + int checked_subid = -1; + + Image *image; +}; + +} // namespace gui +} // namespace openage + +#endif + + diff --git a/cpp/gui/label.cpp b/cpp/gui/label.cpp new file mode 100644 index 0000000000..cdfe309c7b --- /dev/null +++ b/cpp/gui/label.cpp @@ -0,0 +1,31 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "label.h" + +#include "drawer.h" + +namespace openage { +namespace gui { + +Label::Label() = default; + +void Label::set_text(const std::string &text) { + this->text = text; +} + +void Label::set_horizontal_alignment(HorizontalAlignment horizontal_alignment) { + this->horizontal_alignment = horizontal_alignment; +} + +void Label::draw(const Drawer &drawer) const { + if (text.empty()) return; + + assert(height() == default_height()); + + Drawer dr{drawer}; + dr.clip(left(), top(), width(), height()); + dr.draw_text(text, 20, left(), top()); +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/label.h b/cpp/gui/label.h new file mode 100644 index 0000000000..75be707acf --- /dev/null +++ b/cpp/gui/label.h @@ -0,0 +1,38 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_LABEL_H_ +#define OPENAGE_GUI_LABEL_H_ + +#include "control.h" +#include "alignment.h" +#include "../engine.h" + +namespace openage { +namespace gui { + +class Label : public Control { +public: + Label(); + + void set_text(const std::string &text); + + void set_horizontal_alignment(HorizontalAlignment horizontal_alignment); + + void draw(const Drawer &drawer) const override; + + virtual std::tuple get_best_size() const override { + return std::make_tuple(100, default_height()); + } + + static constexpr int default_height() { return 20; } + +protected: + std::string text; + + HorizontalAlignment horizontal_alignment = HorizontalAlignment::LEFT; +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/layout.cpp b/cpp/gui/layout.cpp new file mode 100644 index 0000000000..df3876ee24 --- /dev/null +++ b/cpp/gui/layout.cpp @@ -0,0 +1,20 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "container.h" + +namespace openage { +namespace gui { + +constexpr int LayoutData::INVALID_POS; +constexpr int LayoutData::EVALUATING_POS; + +void Layout::layout(ContainerBase &container) { + layout(container.get_controls(), container.width(), container.height()); +} + +LayoutData *Layout::get_layout_data(const Control *control) { + return control->layout_data.get(); +} + +} // namespace gui +} // namespace openage diff --git a/cpp/gui/layout.h b/cpp/gui/layout.h new file mode 100644 index 0000000000..4572b14c0a --- /dev/null +++ b/cpp/gui/layout.h @@ -0,0 +1,51 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_LAYOUT_H_ +#define OPENAGE_GUI_LAYOUT_H_ + +#include "forward.h" + +#include +#include +#include + +namespace openage { +namespace gui { + +class Layout { +public: + void layout(ContainerBase &container); + virtual void layout(const std::vector> &controls, int container_w, int container_h) = 0; + + virtual std::tuple get_best_size(const std::vector> &controls) = 0; +protected: + LayoutData *get_layout_data(const Control *control); + + int last_width = 0; + int last_height = 0; +}; + +class LayoutData { +public: + virtual ~LayoutData() = default; + + int left() const { return position[0]; } + int right() const { return position[1]; } + int top() const { return position[2]; } + int bottom() const { return position[3]; } + + void invalidate() { + position[3] = INVALID_POS; + } + +protected: + static constexpr int INVALID_POS = std::numeric_limits::min(); + static constexpr int EVALUATING_POS = INVALID_POS + 1; + + std::array position = {{0, 0, 0, INVALID_POS}}; +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/signal.h b/cpp/gui/signal.h new file mode 100644 index 0000000000..10409f3a1d --- /dev/null +++ b/cpp/gui/signal.h @@ -0,0 +1,34 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_GUI_SIGNAL_H_ +#define OPENAGE_GUI_SIGNAL_H_ + +#include +#include + +namespace openage { +namespace gui { + +template +class Signal { +public: + using function_t = std::function; + + void connect(const function_t &handler) { + connections.push_back(handler); + } + + void emit(const Args &... args) { + for (auto &handler : connections) { + handler(args...); + } + } + +protected: + std::vector connections; +}; + +} // namespace gui +} // namespace openage + +#endif diff --git a/cpp/gui/style.cpp b/cpp/gui/style.cpp new file mode 100644 index 0000000000..896f3917e0 --- /dev/null +++ b/cpp/gui/style.cpp @@ -0,0 +1,69 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "style.h" + +#include "border.h" +#include "button.h" +#include "image.h" +#include "label.h" +#include "gridlayout.h" + +namespace openage { +namespace gui { + +std::unique_ptr Style::create_button() const { +#if 0 + auto button = std::make_unique(); + auto border = button->create(); + auto label = border->create(); + + auto texture = asset_manager->get_texture("gui/button.png"); + button->on_state_change([=](Button::State new_state){ + int add_offset = 0; + + switch(new_state) { + case Button::State::DOWN: + border->set_texture(texture, 9); + add_offset = 1; + break; + default: + border->set_texture(texture, 0); + } + +#if 0 + label->set_left(FormAttachment::left().offset_by(10 + add_offset)); + label->set_right(FormAttachment::right().offset_by(-10 + add_offset)); + label->set_bottom(FormAttachment::center().offset_by(10 - add_offset)); +#endif + }); + + button->set_label(label); + + return button; +#endif + return std::unique_ptr(); +} + +std::unique_ptr