UI library prototype wip

This commit is contained in:
Nikita Lisitsa 2021-02-25 09:07:39 +03:00
parent d695c29cc7
commit a53b32067f
27 changed files with 1326 additions and 0 deletions

74
examples/ui.cpp Normal file
View 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
View 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)

View 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; }
};
}

View 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};
};
}

View 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_;
};
}

View 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
};
}

View 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();
};
}

View 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;
};
}

View 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>;
}

View 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();
}

View 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_;
};
}

View 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_;
};
}

View 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() {}
};
}

View 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
};
}

View 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_;
};
}

View 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() {}
};
}

View 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
View 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();
}
}

View 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;
}
}

View 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());
}
}

View 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>();
}
}

View 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));
}
}

View 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
View 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();
}
}

View 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];
}
}

View 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
View 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;
}
}