UI library prototype wip
This commit is contained in:
parent
d695c29cc7
commit
a53b32067f
27 changed files with 1326 additions and 0 deletions
74
examples/ui.cpp
Normal file
74
examples/ui.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#include <psemek/app/app.hpp>
|
||||||
|
#include <psemek/app/main.hpp>
|
||||||
|
|
||||||
|
#include <psemek/gfx/gl.hpp>
|
||||||
|
|
||||||
|
#include <psemek/ui/controller.hpp>
|
||||||
|
#include <psemek/ui/default_element_factory.hpp>
|
||||||
|
#include <psemek/ui/font.hpp>
|
||||||
|
|
||||||
|
#include <psemek/geom/camera.hpp>
|
||||||
|
|
||||||
|
using namespace psemek;
|
||||||
|
|
||||||
|
struct ui_example
|
||||||
|
: app::app
|
||||||
|
{
|
||||||
|
ui_example()
|
||||||
|
: app("UI example", 1)
|
||||||
|
{
|
||||||
|
ui_controller.set_font(ui::make_default_9x12_font());
|
||||||
|
|
||||||
|
auto screen = element_factory.make_screen();
|
||||||
|
screen->add(element_factory.make_button(), ui::screen::x_policy::center, ui::screen::y_policy::center);
|
||||||
|
|
||||||
|
ui_controller.set_root(std::move(screen));
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_resize(int width, int height) override
|
||||||
|
{
|
||||||
|
app::on_resize(width, height);
|
||||||
|
ui_controller.reshape({{{0, width}, {0, height}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_mouse_move(int x, int y, int dx, int dy) override
|
||||||
|
{
|
||||||
|
app::on_mouse_move(x, y, dx, dy);
|
||||||
|
ui_controller.event(ui::mouse_move{{x, y}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_left_button_down() override
|
||||||
|
{
|
||||||
|
app::on_left_button_down();
|
||||||
|
ui_controller.event(ui::mouse_click{ui::mouse_button::left, true});
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_left_button_up() override
|
||||||
|
{
|
||||||
|
app::on_left_button_up();
|
||||||
|
ui_controller.event(ui::mouse_click{ui::mouse_button::left, false});
|
||||||
|
}
|
||||||
|
|
||||||
|
void present() override;
|
||||||
|
|
||||||
|
ui::controller ui_controller;
|
||||||
|
ui::default_element_factory element_factory;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ui_example::present()
|
||||||
|
{
|
||||||
|
gl::ClearColor(0.8f, 0.8f, 0.8f, 1.f);
|
||||||
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
gfx::render_target rt;
|
||||||
|
rt.framebuffer = &gfx::framebuffer::null();
|
||||||
|
rt.draw_buffer = gl::BACK_LEFT;
|
||||||
|
rt.viewport = {{{0, width()}, {0, height()}}};
|
||||||
|
|
||||||
|
ui_controller.render(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return app::main<ui_example>();
|
||||||
|
}
|
||||||
6
libs/ui/CMakeLists.txt
Normal file
6
libs/ui/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
file(GLOB_RECURSE PSEMEK_UI_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSEMEK_UI_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
|
||||||
|
|
||||||
|
psemek_add_library(psemek-ui ${PSEMEK_UI_HEADERS} ${PSEMEK_UI_SOURCES})
|
||||||
|
target_include_directories(psemek-ui PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(psemek-ui PUBLIC psemek-util psemek-log psemek-geom psemek-cg psemek-gfx psemek-async)
|
||||||
23
libs/ui/include/psemek/ui/box_shape.hpp
Normal file
23
libs/ui/include/psemek/ui/box_shape.hpp
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/shape.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct box_shape
|
||||||
|
: shape
|
||||||
|
{
|
||||||
|
geom::box<float, 2> box;
|
||||||
|
|
||||||
|
box_shape() = default;
|
||||||
|
|
||||||
|
box_shape(geom::box<float, 2> box)
|
||||||
|
: box{box}
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool contains(geom::point<float, 2> const & point) const override;
|
||||||
|
geom::box<float, 2> bbox() const override { return box; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
52
libs/ui/include/psemek/ui/button.hpp
Normal file
52
libs/ui/include/psemek/ui/button.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/element.hpp>
|
||||||
|
#include <psemek/ui/label.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct button
|
||||||
|
: element
|
||||||
|
{
|
||||||
|
struct label * label() { return label_.get(); }
|
||||||
|
struct label const * label() const { return label_.get(); }
|
||||||
|
|
||||||
|
children_range children() const override;
|
||||||
|
|
||||||
|
bool on_event(mouse_move const & e) override;
|
||||||
|
bool on_event(mouse_click const & e) override;
|
||||||
|
|
||||||
|
using on_click_callback = std::function<void()>;
|
||||||
|
|
||||||
|
void on_click(on_click_callback callback)
|
||||||
|
{
|
||||||
|
callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum class state_t
|
||||||
|
{
|
||||||
|
normal,
|
||||||
|
mouseover,
|
||||||
|
mousedown,
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual state_t state() const { return state_; }
|
||||||
|
|
||||||
|
virtual void on_state_changed() {}
|
||||||
|
|
||||||
|
void set_label(std::unique_ptr<struct label> label);
|
||||||
|
|
||||||
|
private:
|
||||||
|
state_t state_ = state_t::normal;
|
||||||
|
|
||||||
|
on_click_callback callback_;
|
||||||
|
|
||||||
|
std::unique_ptr<struct label> label_;
|
||||||
|
element * children_[1]{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
32
libs/ui/include/psemek/ui/container.hpp
Normal file
32
libs/ui/include/psemek/ui/container.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/element.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct container
|
||||||
|
: element
|
||||||
|
{
|
||||||
|
children_range children() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::size_t children_count() const { return children_.size(); }
|
||||||
|
|
||||||
|
std::size_t add(std::unique_ptr<element> c);
|
||||||
|
void add(std::unique_ptr<element> c, std::size_t index);
|
||||||
|
|
||||||
|
element * get(std::size_t index);
|
||||||
|
std::optional<std::size_t> find(element * c);
|
||||||
|
|
||||||
|
std::unique_ptr<element> remove(element * c);
|
||||||
|
std::unique_ptr<element> remove(std::size_t index);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<element>> children_;
|
||||||
|
std::vector<element *> children_range_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
32
libs/ui/include/psemek/ui/controller.hpp
Normal file
32
libs/ui/include/psemek/ui/controller.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/element.hpp>
|
||||||
|
#include <psemek/ui/font.hpp>
|
||||||
|
#include <psemek/gfx/render_target.hpp>
|
||||||
|
#include <psemek/util/pimpl.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct controller
|
||||||
|
{
|
||||||
|
controller();
|
||||||
|
~controller();
|
||||||
|
|
||||||
|
std::unique_ptr<element> set_root(std::unique_ptr<element> r);
|
||||||
|
element * root();
|
||||||
|
|
||||||
|
void set_font(std::shared_ptr<font> f);
|
||||||
|
void reshape(geom::box<float, 2> const & shape);
|
||||||
|
|
||||||
|
bool event(mouse_move const & e);
|
||||||
|
bool event(mouse_click const & e);
|
||||||
|
bool event(mouse_wheel const & e);
|
||||||
|
|
||||||
|
void render(gfx::render_target const & rt);
|
||||||
|
|
||||||
|
private:
|
||||||
|
psemek_declare_pimpl
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
15
libs/ui/include/psemek/ui/default_element_factory.hpp
Normal file
15
libs/ui/include/psemek/ui/default_element_factory.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/button.hpp>
|
||||||
|
#include <psemek/ui/screen.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct default_element_factory
|
||||||
|
{
|
||||||
|
std::unique_ptr<button> make_button();
|
||||||
|
std::unique_ptr<screen> make_screen();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
53
libs/ui/include/psemek/ui/element.hpp
Normal file
53
libs/ui/include/psemek/ui/element.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/shape.hpp>
|
||||||
|
#include <psemek/ui/event.hpp>
|
||||||
|
#include <psemek/ui/painter.hpp>
|
||||||
|
|
||||||
|
#include <psemek/async/executor.hpp>
|
||||||
|
|
||||||
|
#include <psemek/util/span.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct element
|
||||||
|
{
|
||||||
|
virtual element * parent() const { return parent_; }
|
||||||
|
virtual void set_parent(element * parent) { parent_ = parent; }
|
||||||
|
|
||||||
|
using children_range = util::span<element * const>;
|
||||||
|
|
||||||
|
virtual children_range children() const { return {}; }
|
||||||
|
|
||||||
|
virtual element * root();
|
||||||
|
virtual element const * root() const;
|
||||||
|
virtual async::executor * loop() const { return loop_; }
|
||||||
|
virtual void set_loop(async::executor * loop) { loop_ = loop; }
|
||||||
|
|
||||||
|
virtual bool on_event(mouse_move const &) { return false; }
|
||||||
|
virtual bool on_event(mouse_click const &) { return false; }
|
||||||
|
virtual bool on_event(mouse_wheel const &) { return false; }
|
||||||
|
|
||||||
|
virtual struct shape const & shape() const = 0;
|
||||||
|
virtual void reshape(geom::box<float, 2> const & bbox) = 0;
|
||||||
|
virtual void reshape();
|
||||||
|
|
||||||
|
virtual geom::box<float, 2> size_constraints() const;
|
||||||
|
|
||||||
|
virtual bool enabled() const { return enabled_; }
|
||||||
|
virtual void set_enabled(bool value) { enabled_ = value; }
|
||||||
|
virtual void enable() { set_enabled(true); }
|
||||||
|
virtual void disable() { set_enabled(false); }
|
||||||
|
|
||||||
|
virtual void draw(painter & p) const = 0;
|
||||||
|
|
||||||
|
virtual ~element() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
element * parent_ = nullptr;
|
||||||
|
async::executor * loop_ = nullptr;
|
||||||
|
bool enabled_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
35
libs/ui/include/psemek/ui/event.hpp
Normal file
35
libs/ui/include/psemek/ui/event.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/geom/point.hpp>
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct mouse_move
|
||||||
|
{
|
||||||
|
geom::point<int, 2> position;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class mouse_button
|
||||||
|
{
|
||||||
|
left,
|
||||||
|
middle,
|
||||||
|
right,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mouse_click
|
||||||
|
{
|
||||||
|
mouse_button button;
|
||||||
|
bool down;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mouse_wheel
|
||||||
|
{
|
||||||
|
int delta;
|
||||||
|
};
|
||||||
|
|
||||||
|
using event_type_list = std::tuple<mouse_move, mouse_click, mouse_wheel>;
|
||||||
|
|
||||||
|
}
|
||||||
70
libs/ui/include/psemek/ui/font.hpp
Normal file
70
libs/ui/include/psemek/ui/font.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/geom/vector.hpp>
|
||||||
|
#include <psemek/geom/box.hpp>
|
||||||
|
|
||||||
|
#include <psemek/gfx/texture.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class font_type
|
||||||
|
{
|
||||||
|
bitmap,
|
||||||
|
sdf,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct character_range
|
||||||
|
{
|
||||||
|
char32_t begin;
|
||||||
|
char32_t end;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct glyph
|
||||||
|
{
|
||||||
|
geom::box<float, 2> position;
|
||||||
|
char32_t character;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct shape_options
|
||||||
|
{
|
||||||
|
enum direction_t
|
||||||
|
{
|
||||||
|
left_to_right,
|
||||||
|
right_to_left,
|
||||||
|
top_to_bottom,
|
||||||
|
bottom_to_top,
|
||||||
|
} direction = left_to_right;
|
||||||
|
|
||||||
|
char32_t unknown_character = '?';
|
||||||
|
|
||||||
|
float scale = 1.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct font
|
||||||
|
{
|
||||||
|
virtual font_type type() const = 0;
|
||||||
|
|
||||||
|
virtual std::string_view name() const = 0;
|
||||||
|
|
||||||
|
virtual geom::vector<int, 2> size() const = 0;
|
||||||
|
|
||||||
|
virtual bool supports_character(char32_t c) const = 0;
|
||||||
|
virtual std::vector<character_range> supported_characters() const = 0;
|
||||||
|
|
||||||
|
virtual std::vector<glyph> shape(std::string_view str, shape_options const & options) const = 0;
|
||||||
|
|
||||||
|
virtual gfx::texture_2d const & atlas() const = 0;
|
||||||
|
|
||||||
|
virtual std::optional<geom::box<float, 2>> texcoords(char32_t character) const = 0;
|
||||||
|
|
||||||
|
virtual ~font() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<font> make_default_9x12_font();
|
||||||
|
|
||||||
|
}
|
||||||
68
libs/ui/include/psemek/ui/label.hpp
Normal file
68
libs/ui/include/psemek/ui/label.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/element.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct label
|
||||||
|
: element
|
||||||
|
{
|
||||||
|
enum class halignment
|
||||||
|
{
|
||||||
|
left,
|
||||||
|
center,
|
||||||
|
right,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class valignment
|
||||||
|
{
|
||||||
|
top,
|
||||||
|
center,
|
||||||
|
bottom,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class multiline_mode
|
||||||
|
{
|
||||||
|
none, // single-line lable
|
||||||
|
minimize_lines, // normal multiline text
|
||||||
|
minimize_area, // as square as possible
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class overflow_mode
|
||||||
|
{
|
||||||
|
ignore,
|
||||||
|
drop,
|
||||||
|
ellipsis,
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void set_text(std::string text);
|
||||||
|
virtual std::string_view text() const { return text_; }
|
||||||
|
|
||||||
|
virtual void set_halign(halignment value);
|
||||||
|
virtual halignment halign() const { return halign_; }
|
||||||
|
|
||||||
|
virtual void set_valign(valignment value);
|
||||||
|
virtual valignment valign() const { return valign_; }
|
||||||
|
|
||||||
|
virtual void set_multiline(multiline_mode value);
|
||||||
|
virtual multiline_mode multiline() const { return multiline_; }
|
||||||
|
|
||||||
|
virtual void set_overflow(overflow_mode value);
|
||||||
|
virtual overflow_mode overflow() const { return overflow_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void on_state_changed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string text_;
|
||||||
|
halignment halign_;
|
||||||
|
valignment valign_;
|
||||||
|
multiline_mode multiline_;
|
||||||
|
overflow_mode overflow_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
36
libs/ui/include/psemek/ui/monospace_font.hpp
Normal file
36
libs/ui/include/psemek/ui/monospace_font.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/font.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct monospace_font
|
||||||
|
: font
|
||||||
|
{
|
||||||
|
monospace_font(character_range range, std::string_view name, geom::vector<int, 2> size, gfx::texture_2d atlas, std::vector<geom::box<float, 2>> texcoords);
|
||||||
|
|
||||||
|
font_type type() const override { return font_type::bitmap; }
|
||||||
|
|
||||||
|
std::string_view name() const override { return name_; }
|
||||||
|
|
||||||
|
geom::vector<int, 2> size() const override { return size_; }
|
||||||
|
|
||||||
|
bool supports_character(char32_t c) const override { return c >= range_.begin && c < range_.end; }
|
||||||
|
std::vector<character_range> supported_characters() const override { return {range_}; }
|
||||||
|
|
||||||
|
std::vector<glyph> shape(std::string_view str, shape_options const & options) const override;
|
||||||
|
|
||||||
|
gfx::texture_2d const & atlas() const override { return atlas_; };
|
||||||
|
|
||||||
|
std::optional<geom::box<float, 2>> texcoords(char32_t character) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
character_range range_;
|
||||||
|
std::string_view name_;
|
||||||
|
geom::vector<int, 2> size_;
|
||||||
|
gfx::texture_2d atlas_;
|
||||||
|
std::vector<geom::box<float, 2>> texcoords_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
19
libs/ui/include/psemek/ui/painter.hpp
Normal file
19
libs/ui/include/psemek/ui/painter.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/font.hpp>
|
||||||
|
#include <psemek/gfx/color.hpp>
|
||||||
|
#include <psemek/geom/box.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct painter
|
||||||
|
{
|
||||||
|
virtual struct font const * font() const = 0;
|
||||||
|
|
||||||
|
virtual void draw_rect(geom::box<float, 2> const & rect, gfx::color_rgba const & color) = 0;
|
||||||
|
|
||||||
|
virtual ~painter() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
30
libs/ui/include/psemek/ui/painter_impl.hpp
Normal file
30
libs/ui/include/psemek/ui/painter_impl.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/painter.hpp>
|
||||||
|
|
||||||
|
#include <psemek/util/pimpl.hpp>
|
||||||
|
|
||||||
|
#include <psemek/geom/matrix.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct painter_impl
|
||||||
|
: painter
|
||||||
|
{
|
||||||
|
painter_impl();
|
||||||
|
~painter_impl();
|
||||||
|
|
||||||
|
void set_font(std::shared_ptr<struct font> font);
|
||||||
|
|
||||||
|
struct font const * font() const override;
|
||||||
|
|
||||||
|
void draw_rect(geom::box<float, 2> const & rect, gfx::color_rgba const & color) override;
|
||||||
|
|
||||||
|
void render(geom::matrix<float, 4, 4> const & transform);
|
||||||
|
|
||||||
|
private:
|
||||||
|
psemek_declare_pimpl
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
51
libs/ui/include/psemek/ui/screen.hpp
Normal file
51
libs/ui/include/psemek/ui/screen.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/container.hpp>
|
||||||
|
#include <psemek/ui/box_shape.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct screen
|
||||||
|
: container
|
||||||
|
{
|
||||||
|
enum class x_policy
|
||||||
|
{
|
||||||
|
left,
|
||||||
|
center,
|
||||||
|
right,
|
||||||
|
floating,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class y_policy
|
||||||
|
{
|
||||||
|
top,
|
||||||
|
center,
|
||||||
|
bottom,
|
||||||
|
floating,
|
||||||
|
};
|
||||||
|
|
||||||
|
void add(std::unique_ptr<element> c, x_policy x, y_policy y);
|
||||||
|
void add(std::unique_ptr<element> c);
|
||||||
|
std::unique_ptr<element> remove(element * c);
|
||||||
|
|
||||||
|
struct shape const & shape() const override { return shape_; }
|
||||||
|
void reshape(geom::box<float, 2> const & bbox) override;
|
||||||
|
|
||||||
|
geom::box<float, 2> size_constraints() const override;
|
||||||
|
|
||||||
|
void draw(painter &) const override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
box_shape shape_;
|
||||||
|
|
||||||
|
struct policy
|
||||||
|
{
|
||||||
|
x_policy x;
|
||||||
|
y_policy y;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<policy> policies_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
17
libs/ui/include/psemek/ui/shape.hpp
Normal file
17
libs/ui/include/psemek/ui/shape.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/geom/point.hpp>
|
||||||
|
#include <psemek/geom/box.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct shape
|
||||||
|
{
|
||||||
|
virtual bool contains(geom::point<float, 2> const & point) const = 0;
|
||||||
|
virtual geom::box<float, 2> bbox() const = 0;
|
||||||
|
|
||||||
|
virtual ~shape() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
13
libs/ui/source/box_shape.cpp
Normal file
13
libs/ui/source/box_shape.cpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#include <psemek/ui/box_shape.hpp>
|
||||||
|
|
||||||
|
#include <psemek/geom/contains.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
bool box_shape::contains(geom::point<float, 2> const & point) const
|
||||||
|
{
|
||||||
|
return geom::contains(box, point);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
74
libs/ui/source/button.cpp
Normal file
74
libs/ui/source/button.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#include <psemek/ui/button.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
element::children_range button::children() const
|
||||||
|
{
|
||||||
|
if (!label_)
|
||||||
|
return {};
|
||||||
|
return children_range{children_};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool button::on_event(mouse_move const & e)
|
||||||
|
{
|
||||||
|
bool const over = shape().contains(geom::cast<float>(e.position));
|
||||||
|
|
||||||
|
switch (state_) {
|
||||||
|
case state_t::normal:
|
||||||
|
if (over)
|
||||||
|
{
|
||||||
|
state_ = state_t::mouseover;
|
||||||
|
on_state_changed();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case state_t::mouseover:
|
||||||
|
case state_t::mousedown:
|
||||||
|
if (!over)
|
||||||
|
{
|
||||||
|
state_ = state_t::normal;
|
||||||
|
on_state_changed();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool button::on_event(mouse_click const & e)
|
||||||
|
{
|
||||||
|
if (e.button != mouse_button::left) return false;
|
||||||
|
|
||||||
|
switch (state_) {
|
||||||
|
case state_t::normal:
|
||||||
|
break;
|
||||||
|
case state_t::mouseover:
|
||||||
|
if (e.down)
|
||||||
|
{
|
||||||
|
state_ = state_t::mousedown;
|
||||||
|
if (callback_)
|
||||||
|
root()->loop()->post(callback_);
|
||||||
|
on_state_changed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case state_t::mousedown:
|
||||||
|
if (!e.down)
|
||||||
|
{
|
||||||
|
state_ = state_t::mouseover;
|
||||||
|
on_state_changed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void button::set_label(std::unique_ptr<struct label> label)
|
||||||
|
{
|
||||||
|
label_ = std::move(label);
|
||||||
|
children_[0] = label_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
65
libs/ui/source/container.cpp
Normal file
65
libs/ui/source/container.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include <psemek/ui/container.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
element::children_range container::children() const
|
||||||
|
{
|
||||||
|
return children_range{children_range_.data(), children_range_.data() + children_range_.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t container::add(std::unique_ptr<element> c)
|
||||||
|
{
|
||||||
|
std::size_t index = 0;
|
||||||
|
while (index < children_.size() && children_[index]) ++index;
|
||||||
|
add(std::move(c), index);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void container::add(std::unique_ptr<element> c, std::size_t index)
|
||||||
|
{
|
||||||
|
if (index >= children_.size())
|
||||||
|
{
|
||||||
|
children_.resize(index + 1);
|
||||||
|
children_range_.resize(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
children_[index] = std::move(c);
|
||||||
|
children_range_[index] = children_[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
element * container::get(std::size_t index)
|
||||||
|
{
|
||||||
|
if (index < children_.size())
|
||||||
|
return children_[index].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t> container::find(element * c)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < children_.size(); ++i)
|
||||||
|
{
|
||||||
|
if (children_[i].get() == c)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<element> container::remove(element * c)
|
||||||
|
{
|
||||||
|
if (auto i = find(c))
|
||||||
|
return remove(*i);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<element> container::remove(std::size_t index)
|
||||||
|
{
|
||||||
|
if (index >= children_.size())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto c = std::move(children_[index]);
|
||||||
|
children_range_[index] = nullptr;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
112
libs/ui/source/controller.cpp
Normal file
112
libs/ui/source/controller.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
#include <psemek/ui/controller.hpp>
|
||||||
|
|
||||||
|
#include <psemek/ui/painter_impl.hpp>
|
||||||
|
#include <psemek/async/event_loop.hpp>
|
||||||
|
#include <psemek/gfx/gl.hpp>
|
||||||
|
#include <psemek/util/recursive.hpp>
|
||||||
|
#include <psemek/geom/camera.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct controller::impl
|
||||||
|
{
|
||||||
|
painter_impl painter;
|
||||||
|
std::unique_ptr<element> root;
|
||||||
|
async::event_loop loop;
|
||||||
|
geom::box<float, 2> shape{{{0.f, 0.f}, {0.f, 0.f}}};
|
||||||
|
|
||||||
|
template <typename E>
|
||||||
|
bool event(E const & e);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename E>
|
||||||
|
bool controller::impl::event(E const & e)
|
||||||
|
{
|
||||||
|
auto visitor = util::recursive([&](auto && self, element * elem) -> bool {
|
||||||
|
for (auto c : elem->children())
|
||||||
|
if (c && self(c)) return true;
|
||||||
|
|
||||||
|
if (elem->enabled() && elem->on_event(e)) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (root)
|
||||||
|
return visitor(root.get());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller::controller()
|
||||||
|
: pimpl_{make_impl()}
|
||||||
|
{}
|
||||||
|
|
||||||
|
controller::~controller() = default;
|
||||||
|
|
||||||
|
std::unique_ptr<element> controller::set_root(std::unique_ptr<element> r)
|
||||||
|
{
|
||||||
|
auto old = std::move(impl().root);
|
||||||
|
impl().root = std::move(r);
|
||||||
|
if (old) old->set_loop(nullptr);
|
||||||
|
if (impl().root)
|
||||||
|
{
|
||||||
|
impl().root->set_loop(&impl().loop);
|
||||||
|
impl().root->reshape(impl().shape);
|
||||||
|
}
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
element * controller::root()
|
||||||
|
{
|
||||||
|
return impl().root.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void controller::set_font(std::shared_ptr<font> f)
|
||||||
|
{
|
||||||
|
impl().painter.set_font(std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
void controller::reshape(geom::box<float, 2> const & shape)
|
||||||
|
{
|
||||||
|
impl().shape = shape;
|
||||||
|
if (impl().root)
|
||||||
|
impl().root->reshape(impl().shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool controller::event(mouse_move const & e)
|
||||||
|
{
|
||||||
|
return impl().event(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool controller::event(mouse_click const & e)
|
||||||
|
{
|
||||||
|
return impl().event(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool controller::event(mouse_wheel const & e)
|
||||||
|
{
|
||||||
|
return impl().event(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void controller::render(gfx::render_target const & rt)
|
||||||
|
{
|
||||||
|
rt.bind();
|
||||||
|
|
||||||
|
gl::Enable(gl::DEPTH_TEST);
|
||||||
|
gl::DepthFunc(gl::LEQUAL);
|
||||||
|
|
||||||
|
gl::Enable(gl::BLEND);
|
||||||
|
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
auto visitor = util::recursive([&](auto && self, element * elem) -> void {
|
||||||
|
elem->draw(impl().painter);
|
||||||
|
for (auto c : elem->children())
|
||||||
|
if (c) self(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (impl().root)
|
||||||
|
visitor(impl().root.get());
|
||||||
|
|
||||||
|
impl().painter.render(geom::window_camera{rt.viewport[0].length(), rt.viewport[1].length()}.transform());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
55
libs/ui/source/default_element_factory.cpp
Normal file
55
libs/ui/source/default_element_factory.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#include <psemek/ui/default_element_factory.hpp>
|
||||||
|
|
||||||
|
#include <psemek/ui/box_shape.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct button_impl
|
||||||
|
: button
|
||||||
|
{
|
||||||
|
struct shape const & shape() const override
|
||||||
|
{
|
||||||
|
return shape_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reshape(geom::box<float, 2> const & bbox) override
|
||||||
|
{
|
||||||
|
shape_.box = bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(painter & p) const override
|
||||||
|
{
|
||||||
|
gfx::color_rgba color = gfx::red;
|
||||||
|
if (state() == state_t::mouseover)
|
||||||
|
color = gfx::light(color);
|
||||||
|
else if (state() == state_t::mousedown)
|
||||||
|
color = gfx::dark(color);
|
||||||
|
p.draw_rect(shape_.box, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
geom::box<float, 2> size_constraints() const override
|
||||||
|
{
|
||||||
|
return {{{100.f, 200.f}, {50.f, 100.f}}};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
box_shape shape_{{{{0.f, 0.f}, {0.f, 0.f}}}};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<button> default_element_factory::make_button()
|
||||||
|
{
|
||||||
|
return std::make_unique<button_impl>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<screen> default_element_factory::make_screen()
|
||||||
|
{
|
||||||
|
return std::make_unique<screen>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
41
libs/ui/source/default_fonts.cpp
Normal file
41
libs/ui/source/default_fonts.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#include <psemek/ui/font.hpp>
|
||||||
|
#include <psemek/ui/monospace_font.hpp>
|
||||||
|
|
||||||
|
#include <psemek/gfx/resource/font_9x12.hpp>
|
||||||
|
|
||||||
|
#include <psemek/util/memory_stream.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_ptr<font> make_default_9x12_font()
|
||||||
|
{
|
||||||
|
character_range range{32, 128};
|
||||||
|
std::string_view name = "default_9x12";
|
||||||
|
geom::vector<int, 2> size{9, 12};
|
||||||
|
|
||||||
|
util::memory_istream is{gfx::resource::font_9x12};
|
||||||
|
gfx::texture_2d atlas = gfx::texture_2d::from_pixmap(gfx::read_pbm(is));
|
||||||
|
atlas.nearest_filter();
|
||||||
|
atlas.clamp();
|
||||||
|
|
||||||
|
std::vector<geom::box<float, 2>> texcoords(range.end - range.begin);
|
||||||
|
|
||||||
|
for (char32_t c = range.begin; c < range.end; ++c)
|
||||||
|
{
|
||||||
|
int const row = 16;
|
||||||
|
int x = (c - range.begin) / row;
|
||||||
|
int y = (c - range.begin) % row;
|
||||||
|
|
||||||
|
geom::box<float, 2> b;
|
||||||
|
b[0].min = x * 11 + 1;
|
||||||
|
b[0].max = b[0].min + 9;
|
||||||
|
b[1].min = y * 14 + 1;
|
||||||
|
b[1].max = b[1].min + 12;
|
||||||
|
texcoords[c - range.begin] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<monospace_font>(range, name, size, std::move(atlas), std::move(texcoords));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
libs/ui/source/element.cpp
Normal file
31
libs/ui/source/element.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <psemek/ui/element.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
element * element::root()
|
||||||
|
{
|
||||||
|
element * r = this;
|
||||||
|
while (r->parent()) r = r->parent();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
element const * element::root() const
|
||||||
|
{
|
||||||
|
element const * r = this;
|
||||||
|
while (r->parent()) r = r->parent();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void element::reshape()
|
||||||
|
{
|
||||||
|
reshape(shape().bbox());
|
||||||
|
}
|
||||||
|
|
||||||
|
geom::box<float, 2> element::size_constraints() const
|
||||||
|
{
|
||||||
|
static float const inf = std::numeric_limits<float>::infinity();
|
||||||
|
return {{{0.f, inf}, {0.f, inf}}};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
38
libs/ui/source/label.cpp
Normal file
38
libs/ui/source/label.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include <psemek/ui/label.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
// TODO: changes should notify parent about content change
|
||||||
|
|
||||||
|
void label::set_text(std::string text)
|
||||||
|
{
|
||||||
|
text_ = std::move(text);
|
||||||
|
on_state_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void label::set_halign(halignment value)
|
||||||
|
{
|
||||||
|
halign_ = value;
|
||||||
|
on_state_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void label::set_valign(valignment value)
|
||||||
|
{
|
||||||
|
valign_ = value;
|
||||||
|
on_state_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void label::set_multiline(multiline_mode value)
|
||||||
|
{
|
||||||
|
multiline_ = value;
|
||||||
|
on_state_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void label::set_overflow(overflow_mode value)
|
||||||
|
{
|
||||||
|
overflow_ = value;
|
||||||
|
on_state_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
63
libs/ui/source/monospace_font.cpp
Normal file
63
libs/ui/source/monospace_font.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include <psemek/ui/monospace_font.hpp>
|
||||||
|
|
||||||
|
#include <psemek/util/unicode.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
monospace_font::monospace_font(character_range range, std::string_view name, geom::vector<int, 2> size, gfx::texture_2d atlas, std::vector<geom::box<float, 2>> texcoords)
|
||||||
|
: range_{range}
|
||||||
|
, name_{name}
|
||||||
|
, size_{size}
|
||||||
|
, atlas_{std::move(atlas)}
|
||||||
|
, texcoords_{std::move(texcoords)}
|
||||||
|
{
|
||||||
|
if (range_.end - range_.begin != texcoords_.size())
|
||||||
|
throw std::runtime_error("Wrong number of texture coordinates");
|
||||||
|
|
||||||
|
if (!supports_character('?'))
|
||||||
|
throw std::runtime_error("Monospace font must support '?' character");
|
||||||
|
}
|
||||||
|
|
||||||
|
static geom::vector<float, 2> advance_dir(shape_options::direction_t direction)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case shape_options::left_to_right: return {1.f, 0.f};
|
||||||
|
case shape_options::right_to_left: return {-1.f, 0.f};
|
||||||
|
case shape_options::top_to_bottom: return {0.f, 1.f};
|
||||||
|
case shape_options::bottom_to_top: return {0.f, -1.f};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {1.f, 0.f};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<glyph> monospace_font::shape(std::string_view str, shape_options const & options) const
|
||||||
|
{
|
||||||
|
char32_t const unknown = supports_character(options.unknown_character) ? options.unknown_character : '?';
|
||||||
|
geom::vector<float, 2> const size = geom::cast<float>(this->size()) * options.scale;
|
||||||
|
geom::vector<float, 2> const advance = geom::pointwise_mult(advance_dir(options.direction), size);
|
||||||
|
|
||||||
|
std::vector<glyph> result;
|
||||||
|
|
||||||
|
geom::vector<float, 2> pos{0.f, 0.f};
|
||||||
|
for (char32_t c : util::utf8_range(str))
|
||||||
|
{
|
||||||
|
glyph g;
|
||||||
|
g.character = supports_character(c) ? c : unknown;
|
||||||
|
g.position = {{{pos[0], pos[0] + size[0]}, {pos[1], pos[1] + size[1]}}};
|
||||||
|
result.push_back(g);
|
||||||
|
pos += advance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<geom::box<float, 2>> monospace_font::texcoords(char32_t c) const
|
||||||
|
{
|
||||||
|
if (!supports_character(c)) return std::nullopt;
|
||||||
|
|
||||||
|
return texcoords_[c - range_.begin];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
126
libs/ui/source/painter_impl.cpp
Normal file
126
libs/ui/source/painter_impl.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
#include <psemek/ui/painter_impl.hpp>
|
||||||
|
|
||||||
|
#include <psemek/gfx/program.hpp>
|
||||||
|
#include <psemek/gfx/array.hpp>
|
||||||
|
#include <psemek/gfx/buffer.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
static char const colored_vs[] =
|
||||||
|
R"(#version 330
|
||||||
|
|
||||||
|
uniform mat4 u_transform;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_position;
|
||||||
|
layout (location = 1) in uint in_depth;
|
||||||
|
layout (location = 2) in vec4 in_color;
|
||||||
|
|
||||||
|
out vec4 color;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = u_transform * vec4(in_position, float(in_depth) / 16777216.0 * 2.0 - 1.0, 1.0);
|
||||||
|
color = in_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static char const colored_fs[] =
|
||||||
|
R"(#version 330
|
||||||
|
|
||||||
|
in vec4 color;
|
||||||
|
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
out_color = color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
struct colored_vertex
|
||||||
|
{
|
||||||
|
geom::point<float, 2> position;
|
||||||
|
std::uint32_t depth;
|
||||||
|
gfx::color_rgba color;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(colored_vertex) == 16);
|
||||||
|
|
||||||
|
struct painter_impl::impl
|
||||||
|
{
|
||||||
|
std::shared_ptr<struct font> font;
|
||||||
|
std::uint32_t depth = 0;
|
||||||
|
|
||||||
|
gfx::program colored_program;
|
||||||
|
gfx::array colored_vao;
|
||||||
|
gfx::buffer colored_vbo;
|
||||||
|
gfx::buffer colored_ebo;
|
||||||
|
|
||||||
|
std::vector<colored_vertex> colored_vertices;
|
||||||
|
std::vector<std::uint32_t> colored_indices;
|
||||||
|
|
||||||
|
impl();
|
||||||
|
};
|
||||||
|
|
||||||
|
painter_impl::impl::impl()
|
||||||
|
: colored_program{colored_vs, colored_fs}
|
||||||
|
{
|
||||||
|
colored_vao.bind();
|
||||||
|
colored_vbo.bind();
|
||||||
|
gl::EnableVertexAttribArray(0);
|
||||||
|
gl::VertexAttribPointer(0, 2, gl::FLOAT, gl::FALSE, sizeof(colored_vertex), reinterpret_cast<void const *>(0));
|
||||||
|
gl::EnableVertexAttribArray(1);
|
||||||
|
gl::VertexAttribIPointer(1, 1, gl::UNSIGNED_INT, sizeof(colored_vertex), reinterpret_cast<void const *>(8));
|
||||||
|
gl::EnableVertexAttribArray(2);
|
||||||
|
gl::VertexAttribPointer(2, 4, gl::UNSIGNED_BYTE, gl::TRUE, sizeof(colored_vertex), reinterpret_cast<void const *>(12));
|
||||||
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, colored_ebo.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
painter_impl::painter_impl()
|
||||||
|
: pimpl_{make_impl()}
|
||||||
|
{}
|
||||||
|
|
||||||
|
painter_impl::~painter_impl() = default;
|
||||||
|
|
||||||
|
void painter_impl::set_font(std::shared_ptr<struct font> font)
|
||||||
|
{
|
||||||
|
impl().font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
font const * painter_impl::font() const
|
||||||
|
{
|
||||||
|
return impl().font.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void painter_impl::draw_rect(geom::box<float, 2> const & rect, gfx::color_rgba const & color)
|
||||||
|
{
|
||||||
|
std::uint32_t const depth = impl().depth++;
|
||||||
|
std::uint32_t const base = impl().colored_vertices.size();
|
||||||
|
|
||||||
|
impl().colored_vertices.push_back({rect.corner(0.f, 0.f), depth, color});
|
||||||
|
impl().colored_vertices.push_back({rect.corner(1.f, 0.f), depth, color});
|
||||||
|
impl().colored_vertices.push_back({rect.corner(0.f, 1.f), depth, color});
|
||||||
|
impl().colored_vertices.push_back({rect.corner(1.f, 1.f), depth, color});
|
||||||
|
|
||||||
|
impl().colored_indices.insert(impl().colored_indices.end(), {base + 0, base + 1, base + 2, base + 2, base + 1, base + 3});
|
||||||
|
}
|
||||||
|
|
||||||
|
void painter_impl::render(geom::matrix<float, 4, 4> const & transform)
|
||||||
|
{
|
||||||
|
impl().colored_vbo.load(impl().colored_vertices);
|
||||||
|
impl().colored_ebo.load(impl().colored_indices);
|
||||||
|
|
||||||
|
int const colored_count = impl().colored_indices.size();
|
||||||
|
|
||||||
|
impl().colored_vertices.clear();
|
||||||
|
impl().colored_indices.clear();
|
||||||
|
|
||||||
|
impl().depth = 0;
|
||||||
|
|
||||||
|
impl().colored_program.bind();
|
||||||
|
impl().colored_program["u_transform"] = transform;
|
||||||
|
impl().colored_vao.bind();
|
||||||
|
gl::DrawElements(gl::TRIANGLES, colored_count, gl::UNSIGNED_INT, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
95
libs/ui/source/screen.cpp
Normal file
95
libs/ui/source/screen.cpp
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
#include <psemek/ui/screen.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
void screen::add(std::unique_ptr<element> c, x_policy x, y_policy y)
|
||||||
|
{
|
||||||
|
auto i = container::add(std::move(c));
|
||||||
|
if (i >= policies_.size())
|
||||||
|
policies_.resize(i + 1);
|
||||||
|
policies_[i] = {x, y};
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen::add(std::unique_ptr<element> c)
|
||||||
|
{
|
||||||
|
add(std::move(c), x_policy::floating, y_policy::floating);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<element> screen::remove(element * c)
|
||||||
|
{
|
||||||
|
return container::remove(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void screen::reshape(geom::box<float, 2> const & bbox)
|
||||||
|
{
|
||||||
|
shape_.box = bbox;
|
||||||
|
auto cs = children();
|
||||||
|
for (std::size_t i = 0; i < cs.size(); ++i)
|
||||||
|
{
|
||||||
|
auto c = cs[i];
|
||||||
|
if (!c) continue;
|
||||||
|
auto sc = c->size_constraints();
|
||||||
|
auto size = bbox.dimensions();
|
||||||
|
size[0] = std::min(size[0], sc[0].min);
|
||||||
|
size[1] = std::min(size[1], sc[1].min);
|
||||||
|
|
||||||
|
auto m = bbox.center();
|
||||||
|
|
||||||
|
geom::box<float, 2> cbox = c->shape().bbox();
|
||||||
|
|
||||||
|
switch (policies_[i].x)
|
||||||
|
{
|
||||||
|
case x_policy::left:
|
||||||
|
cbox[0] = {bbox[0].min, bbox[0].min + size[0]};
|
||||||
|
break;
|
||||||
|
case x_policy::center:
|
||||||
|
cbox[0] = {m[0] - size[0] / 2.f, m[0] + size[0] / 2.f};
|
||||||
|
break;
|
||||||
|
case x_policy::right:
|
||||||
|
cbox[0] = {bbox[0].max - size[0], bbox[0].max};
|
||||||
|
break;
|
||||||
|
case x_policy::floating:
|
||||||
|
cbox[0] = geom::expand(cbox[0], (size[0] - cbox[0].length()) / 2.f);
|
||||||
|
|
||||||
|
if (cbox[0].min < bbox[0].min)
|
||||||
|
cbox[0] += (bbox[0].min - cbox[0].min);
|
||||||
|
else if (cbox[0].max > bbox[0].max)
|
||||||
|
cbox[0] -= (cbox[0].max - bbox[0].max);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (policies_[i].y)
|
||||||
|
{
|
||||||
|
case y_policy::top:
|
||||||
|
cbox[1] = {bbox[1].min, bbox[1].min + size[1]};
|
||||||
|
break;
|
||||||
|
case y_policy::center:
|
||||||
|
cbox[1] = {m[1] - size[1] / 2.f, m[1] + size[1] / 2.f};
|
||||||
|
break;
|
||||||
|
case y_policy::bottom:
|
||||||
|
cbox[1] = {bbox[1].max - size[1], bbox[1].max};
|
||||||
|
break;
|
||||||
|
case y_policy::floating:
|
||||||
|
cbox[1] = geom::expand(cbox[1], (size[1] - cbox[1].length()) / 2.f);
|
||||||
|
|
||||||
|
if (cbox[1].min < bbox[1].min)
|
||||||
|
cbox[1] += (bbox[1].min - cbox[1].min);
|
||||||
|
else if (cbox[1].max > bbox[1].max)
|
||||||
|
cbox[1] -= (cbox[1].max - bbox[1].max);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->reshape(cbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geom::box<float, 2> screen::size_constraints() const
|
||||||
|
{
|
||||||
|
geom::box<float, 2> result = element::size_constraints();
|
||||||
|
for (auto c : children())
|
||||||
|
if (c) result &= c->size_constraints();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue