text, hover, styles and buttons!

This commit is contained in:
hippoz 2022-03-18 02:49:16 +02:00
parent 9fe7659a0f
commit ef85745f58
Signed by: hippoz
GPG key ID: 7C52899193467641
18 changed files with 404 additions and 64 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
builddir/
.cache/
compile_commands.json

View file

@ -1,4 +1,5 @@
#include "Box.hpp"
#include "Point.hpp"
namespace Raven {
@ -6,6 +7,10 @@ bool Box::contains_point(double x, double y) {
return x >= m_x && m_x + m_width >= x && y >= m_y && m_y + m_height >= y;
}
bool Box::contains_point(Point &point) {
return point.get_x() >= m_x && m_x + m_width >= point.get_x() && point.get_y() >= m_y && m_y + m_height >= point.get_y();
}
bool Box::contains_box(Box &other) const {
double ax1 = m_x;
double ax2 = m_x + m_width;

View file

@ -1,5 +1,7 @@
#pragma once
#include "Point.hpp"
namespace Raven {
class Box {
@ -30,6 +32,7 @@ public:
void set_height(double height) { m_height = height; }
bool contains_point(double x, double y);
bool contains_point(Point &point);
bool contains_box(Box &other) const;
};

View file

@ -1,16 +1,35 @@
#include <iostream>
#include "Button.hpp"
#include "Box.hpp"
#include "Window.hpp"
#include "src/Painter.hpp"
#include <pango/pangocairo.h>
namespace Raven {
void Button::on_init() {
set_style_do_background_fill(true);
set_style_background_fill_color(m_style_normal_background_fill_color);
set_style_font_description(get_top_level_styles()->get_style_controls_font_description());
set_did_init(true);
}
void Button::on_paint() {
auto painter = get_window()->get_painter();
auto cr = painter.get_cairo();
cr->set_source_rgb(0.866, 0.713, 0.949);
painter.rounded_rectangle(get_current_geometry(), 8);
cr->fill();
painter.source_rgb(m_style_text_fill_color);
painter.set_pango_font_description(m_style_font_description);
painter.text(get_current_geometry(), m_text, PaintTextAlign::Center);
painter.fill();
}
void Button::on_focus_update(FocusUpdateEvent &event) {
if (is_focused()) {
set_style_background_fill_color(m_style_focused_background_fill_color);
} else {
set_style_background_fill_color(m_style_normal_background_fill_color);
}
}
}

View file

@ -1,22 +1,35 @@
#include <string>
#include "Widget.hpp"
#include "RGB.hpp"
#include "pango/pango-font.h"
#include "Window.hpp"
#pragma once
namespace Raven {
class Button : public Widget {
DEF_STYLE(text_fill_color, RGB, 0.0, 0.0, 0.0)
DEF_STYLE(normal_background_fill_color, RGB, 0.6941, 0.3843, 0.5254)
DEF_STYLE(focused_background_fill_color, RGB, 0.5607, 0.2470, 0.4431)
DEF_STYLE(active_background_fill_color, RGB, 0.896, 0.743, 0.979)
DEF_STYLE(font_description, PangoFontDescription*, nullptr)
private:
std::string m_text;
public:
Button(std::string text)
: m_text(text)
, Widget() {}
: Widget()
, m_text(text) {}
void set_text(std::string text) { m_text = text; wants_repaint(); }
std::string &get_text() { return m_text; }
void on_paint();
void on_init();
void on_focus_update(FocusUpdateEvent &event);
};
}

View file

@ -11,7 +11,9 @@ enum class EventType {
MouseButton,
MouseMove,
WidgetRepaintRequested
WidgetRepaintRequested,
FocusUpdate,
ActivationUpdate
};
class Event {
@ -20,11 +22,12 @@ private:
public:
Event() {}
void accept() { m_accepted = true; }
bool is_accepted() { return m_accepted; }
virtual EventType get_type() { return EventType::NoneEvent; }
virtual const char *get_name() { return "NoneEvent"; }
void accept() { m_accepted = true; }
bool get_accepted() { return m_accepted; }
virtual ~Event() = default;
};
@ -70,4 +73,30 @@ public:
Box &get_repaint_area() { return m_repaint_area; }
};
class FocusUpdateEvent : public Event {
private:
bool m_focus_status;
public:
FocusUpdateEvent(bool focus_status)
: m_focus_status(focus_status) {}
EventType get_type() { return EventType::FocusUpdate; }
const char *get_name() { return "FocusUpdate"; }
bool get_focus_status() { return m_focus_status; }
};
class ActivationUpdateEvent : public Event {
private:
bool m_activation_status;
public:
ActivationUpdateEvent(bool activation_status)
: m_activation_status(activation_status) {}
EventType get_type() { return EventType::ActivationUpdate; }
const char *get_name() { return "ActivationUpdate"; }
bool get_activation_status() { return m_activation_status; }
};
}

View file

@ -4,6 +4,7 @@ namespace Raven {
class Events;
class Painter;
class Point;
class TopLevelStyles;
class Widget;
class Window;

View file

@ -1,4 +1,5 @@
#include "Painter.hpp"
#include "src/RGB.hpp"
namespace Raven {
@ -19,4 +20,60 @@ void Painter::rounded_rectangle(Box &geometry, double border_radius) {
m_cairo->close_path();
}
bool Painter::text(Point &where, std::string &text) {
if (m_pango_font_description == nullptr)
return false;
PangoLayout *layout = pango_cairo_create_layout(m_cairo->cobj());
pango_layout_set_font_description(layout, m_pango_font_description);
pango_layout_set_text(layout, text.c_str(), -1);
m_cairo->move_to(where.get_x(), where.get_y());
pango_cairo_show_layout(m_cairo->cobj(), layout);
g_object_unref(layout);
return true;
}
bool Painter::text(Box &geometry, std::string &text, PaintTextAlign align) {
if (m_pango_font_description == nullptr)
return false;
PangoLayout *layout = pango_cairo_create_layout(m_cairo->cobj());
int font_width;
int font_height;
pango_layout_set_font_description(layout, m_pango_font_description);
pango_layout_set_text(layout, text.c_str(), -1);
pango_layout_get_pixel_size(layout, &font_width, &font_height);
double x = -1;
double y = geometry.get_y() + ((geometry.get_height() - font_height) / 2);
if (align == PaintTextAlign::Center) {
x = geometry.get_x() + ((geometry.get_width() - font_width) / 2);
} else {
x = 0;
}
m_cairo->move_to(x, y);
pango_cairo_show_layout(m_cairo->cobj(), layout);
g_object_unref(layout);
return true;
}
void Painter::source_rgb(RGB &source_rgb) {
m_cairo->set_source_rgb(source_rgb.get_r(), source_rgb.get_g(), source_rgb.get_b());
}
void Painter::fill() {
m_cairo->fill();
}
}

View file

@ -1,20 +1,37 @@
#pragma once
#include "Box.hpp"
#include "Point.hpp"
#include "src/RGB.hpp"
#include <cairomm/cairomm.h>
#include <pango/pangocairo.h>
namespace Raven {
enum class PaintTextAlign {
Center = 0,
Left
};
class Painter {
private:
Cairo::RefPtr<Cairo::Context> m_cairo;
PangoFontDescription *m_pango_font_description;
public:
Painter() {}
Cairo::RefPtr<Cairo::Context> get_cairo() { return m_cairo; }
void set_cairo(Cairo::RefPtr<Cairo::Context> cairo) { m_cairo = cairo; }
void set_pango_font_description(PangoFontDescription *pango_font_description) { m_pango_font_description = pango_font_description; }
PangoFontDescription *get_pango_font_description() { return m_pango_font_description; }
void rounded_rectangle(Box &geometry, double border_radius);
bool text(Point &where, std::string &text);
bool text(Box &geometry, std::string &text, PaintTextAlign align);
void source_rgb(RGB &source_rgb);
void fill();
};
}

26
src/RGB.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
namespace Raven {
class RGB {
private:
double m_r {0.0};
double m_g {0.0};
double m_b {0.0};
public:
RGB() {}
RGB(double r, double g, double b)
: m_r(r)
, m_g(g)
, m_b(b) {}
void set_r(double r) { m_r = r; }
void set_g(double g) { m_g = g; }
void set_b(double b) { m_b = b; }
double get_r() { return m_r; }
double get_g() { return m_g; }
double get_b() { return m_b; }
};
}

9
src/StyleMacros.hpp Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#define DEF_STYLE(name, type, ...) \
private: \
type m_style_##name {__VA_ARGS__}; \
public: \
void set_style_##name(type new_style_value) { m_style_##name = new_style_value; wants_repaint(); } \
type get_style_##name() { return m_style_##name; } \
private:

12
src/TopLevelStyles.cpp Normal file
View file

@ -0,0 +1,12 @@
#include "TopLevelStyles.hpp"
#include "Window.hpp"
namespace Raven {
void TopLevelStyles::wants_repaint() {
// when one of the styles here changes, we will dispatch
// a full repaint to the entire window :^)
m_window->dispatch_full_repaint();
}
}

40
src/TopLevelStyles.hpp Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include <iostream>
#include "Forward.hpp"
#include "pango/pango-font.h"
#include "StyleMacros.hpp"
namespace Raven {
class TopLevelStyles {
DEF_STYLE(controls_font_description, PangoFontDescription*, nullptr)
private:
Window *m_window;
public:
TopLevelStyles(Window *window)
: m_window(window)
{
PangoFontDescription *font_description = pango_font_description_new();
pango_font_description_set_family(font_description, "sans-serif");
pango_font_description_set_weight(font_description, PANGO_WEIGHT_NORMAL);
pango_font_description_set_absolute_size(font_description, 16 * PANGO_SCALE);
m_style_controls_font_description = font_description;
}
~TopLevelStyles() {
if (m_style_controls_font_description) {
pango_font_description_free(m_style_controls_font_description);
}
}
Window *get_window() { return m_window; }
void set_window(Window *window) { m_window = window; }
void wants_repaint();
};
}

View file

@ -6,6 +6,13 @@
namespace Raven {
void Widget::set_window(Window *window) {
m_window = window;
m_top_level_styles = m_window->get_top_level_styles();
on_init();
}
void Widget::wants_repaint() {
if (!m_window)
return;
@ -29,72 +36,111 @@ void Widget::remove_child(Widget *child) {
m_children.erase(std::remove(m_children.begin(), m_children.end(), child), m_children.end());
}
void Widget::do_generic_paint() {
if (m_style_do_background_fill) {
auto painter = m_window->get_painter();
auto cr = painter.get_cairo();
cr->save();
painter.source_rgb(m_style_background_fill_color);
painter.rounded_rectangle(m_current_geometry, m_style_background_border_radius);
painter.fill();
cr->restore();
}
}
void Widget::handle_repaint_requested(WidgetRepaintRequestedEvent &event) {
std::cout << "-------\n";
std::cout << "-- repaint_area: "
<< event.get_repaint_area().get_x()
<< ", "
<< event.get_repaint_area().get_y()
<< ", "
<< event.get_repaint_area().get_width()
<< ", "
<< event.get_repaint_area().get_height()
<< " --\n";
std::cout << "-- m_current_geometry: "
<< m_current_geometry.get_x()
<< ", "
<< m_current_geometry.get_y()
<< ", "
<< m_current_geometry.get_width()
<< ", "
<< m_current_geometry.get_height()
<< " --\n";
std::cout << "-------\n";
if (!m_did_init)
return;
// widgets contain all of thier children inside their geometry
// we will check if the event's repaint area (the area that needs to be repainted :))
// overlaps with our geometry. If it does, we will proceed with the repaint process
if (!event.get_repaint_area().contains_box(m_current_geometry)) {
event.accept(); // consume this event so that it won't bubble down to the other children
return;
}
std::cout << "--- PASSED\n";
// before calling this widget's paint function, we
// will perform generic widget painting behavior (background color, etc)
do_generic_paint();
on_paint(); // we're going to call this widget's paint function
// we're going to call this widget's paint handler function
// we will also save and then restore the cairo context
// to prevent the state (e.g. source rgb) for leaking onto other widgets
auto painter = m_window->get_painter();
auto cr = painter.get_cairo();
// tell all children about this event
// the repaint area matching the geometry is checked at the start of
// this function, which means they'll repaint as well if it is needed
for (auto& child : m_children) {
child->dispatch_event(event);
cr->save();
on_paint();
cr->restore();
// we are not going to call accept() on this event as we want it to bubble down
// to all the other relevant children
}
void Widget::handle_mouse_move_event(MouseMoveEvent &event) {
bool update_focus_to = true;
if (!m_current_geometry.contains_point(event.get_point())) {
// we just became unfocused
if (m_is_focused) {
update_focus_to = false;
} else {
return;
}
}
m_is_focused = update_focus_to;
auto focus_update_event = FocusUpdateEvent(update_focus_to);
on_mouse_move(event);
on_focus_update(focus_update_event);
if (m_consumes_hits)
event.accept();
}
void Widget::handle_mouse_button_event(MouseButtonEvent &event) {
// the mouse has updated its click state in our bounds
m_is_active = event.get_was_left_button_pressed();
auto activation_update_event = ActivationUpdateEvent(m_is_active);
on_mouse_button(event);
on_activation_update(activation_update_event);
if (m_consumes_hits)
event.accept();
}
void Widget::dispatch_event(Event &event) {
process_event(event);
}
void Widget::process_event(Event &event) {
switch (event.get_type()) {
case EventType::MouseMove: {
on_mouse_move(reinterpret_cast<MouseMoveEvent&>(event));
handle_mouse_move_event(reinterpret_cast<MouseMoveEvent&>(event));
break;
}
case EventType::MouseButton: {
on_mouse_button(reinterpret_cast<MouseButtonEvent&>(event));
handle_mouse_button_event(reinterpret_cast<MouseButtonEvent&>(event));
break;
}
case EventType::WidgetRepaintRequested: {
handle_repaint_requested(reinterpret_cast<WidgetRepaintRequestedEvent&>(event));
break;
}
/* these events aren't handled here, as they won't be dispatched to us from other places */
case EventType::FocusUpdate:
case EventType::ActivationUpdate:
case EventType::NoneEvent: {
break;
}
}
if (!event.get_accepted()) {
for (auto& child : m_children) {
child->dispatch_event(event);
}
}
}
}

View file

@ -1,18 +1,34 @@
#pragma once
#include <memory>
#include <vector>
#include <string>
#include "Box.hpp"
#include "Events.hpp"
#include "Forward.hpp"
#include "RGB.hpp"
#include "TopLevelStyles.hpp"
#include "StyleMacros.hpp"
namespace Raven {
class Widget {
DEF_STYLE(do_background_fill, bool, false)
DEF_STYLE(background_fill_color, RGB, 0, 0, 0)
DEF_STYLE(background_border_radius, double, 0.0)
private:
Box m_current_geometry {};
std::vector<Widget*> m_children;
Widget *m_parent { nullptr };
Window *m_window { nullptr };
std::shared_ptr<TopLevelStyles> m_top_level_styles = nullptr;
bool m_did_init { false };
bool m_is_focused { false };
bool m_is_active { false };
bool m_consumes_hits { false };
public:
Widget() {}
@ -27,21 +43,39 @@ public:
void set_parent(Widget *parent) { m_parent = parent; }
Window *get_window() { return m_window; }
void set_window(Window *window) { m_window = window; }
void set_window(Window *window);
std::shared_ptr<TopLevelStyles> get_top_level_styles() { return m_top_level_styles; }
void set_top_level_styles(std::shared_ptr<TopLevelStyles> top_level_styles) { m_top_level_styles = top_level_styles; }
void set_did_init(bool did_init) { m_did_init = did_init; }
bool get_did_init() { return m_did_init; }
bool is_focused() { return m_is_focused; }
bool is_active() { return m_is_active; }
bool get_consumes_hits() { return m_consumes_hits; }
void set_consumes_hits(bool consumes_hits) { m_consumes_hits = consumes_hits; }
void dispatch_event(Event &event);
void wants_repaint();
Widget *walk_until_top_level();
virtual void on_mouse_button(MouseButtonEvent &event) {};
virtual void on_mouse_move(MouseMoveEvent &event) {};
virtual void on_paint() {};
virtual void on_init() { set_did_init(true); }
virtual void on_mouse_button(MouseButtonEvent &event) {}
virtual void on_mouse_move(MouseMoveEvent &event) {}
virtual void on_focus_update(FocusUpdateEvent &event) {}
virtual void on_activation_update(ActivationUpdateEvent &event) {}
virtual void on_paint() {}
virtual ~Widget() {};
private:
void process_event(Event &event);
void handle_repaint_requested(WidgetRepaintRequestedEvent &event);
void do_generic_paint();
void handle_mouse_move_event(MouseMoveEvent &event);
void handle_mouse_button_event(MouseButtonEvent &event);
};
}

View file

@ -9,6 +9,7 @@
#include "Window.hpp"
#include "Events.hpp"
#include "src/Point.hpp"
namespace Raven {
@ -45,17 +46,21 @@ bool Window::spawn_window() {
return true;
}
void Window::dispatch_repaint_on_box(Box box) {
bool Window::dispatch_to_main_widget(Event &event) {
if (!m_main_widget)
return;
auto event = WidgetRepaintRequestedEvent(std::move(box));
return false;
m_main_widget->dispatch_event(event);
return true;
}
void Window::dispatch_full_repaint() {
dispatch_repaint_on_box(m_current_geometry);
bool Window::dispatch_repaint_on_box(Box box) {
auto event = WidgetRepaintRequestedEvent(std::move(box));
return dispatch_to_main_widget(event);
}
bool Window::dispatch_full_repaint() {
return dispatch_repaint_on_box(m_current_geometry);
}
void Window::run(bool block) {
@ -68,13 +73,23 @@ void Window::run(bool block) {
break;
switch (e.type) {
case MapNotify:
case Expose:
std::cout << "gotta repaint!\n";
case MapNotify: {
dispatch_full_repaint();
break;
default:
}
case Expose: {
auto box = Box(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height);
dispatch_repaint_on_box(std::move(box));
break;
}
case MotionNotify: {
auto point = Point(e.xmotion.x, e.xmotion.y);
auto event = MouseMoveEvent(std::move(point));
dispatch_to_main_widget(event);
}
default: {
break;
}
}
}
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <memory>
#include "Widget.hpp"
#include "Painter.hpp"
#include <X11/Xlib.h>
@ -12,6 +13,7 @@ private:
Widget *m_main_widget { nullptr };
Box m_current_geometry { 0, 0, 800, 600 };
Painter m_painter {};
std::shared_ptr<TopLevelStyles> m_top_level_styles = std::make_shared<TopLevelStyles>(this);
Display *m_x_display { nullptr };
public:
@ -25,9 +27,13 @@ public:
Widget *get_main_widget() { return m_main_widget; }
void set_main_widget(Widget *main_widget);
// TODO: add setter for top_level_styles
std::shared_ptr<TopLevelStyles> get_top_level_styles() { return m_top_level_styles; }
void dispatch_repaint_on_box(Box box);
void dispatch_full_repaint();
bool dispatch_repaint_on_box(Box box);
bool dispatch_full_repaint();
bool dispatch_to_main_widget(Event &event);
Box &get_current_geometry() { return m_current_geometry; }

View file

@ -6,12 +6,18 @@
int main() {
Raven::Window window {};
Raven::Widget main_widget {};
Raven::Button button {"click me!"};
window.spawn_window();
button.set_current_geometry(Raven::Box(10, 10, 100, 30));
window.set_main_widget(&button);
main_widget.set_current_geometry(window.get_current_geometry());
main_widget.set_style_background_fill_color(Raven::RGB(0.9764, 0.9607, 0.8431));
main_widget.set_style_do_background_fill(true);
window.set_main_widget(&main_widget);
main_widget.add_child(&button);
window.run(true);
return 0;