diff --git a/meson.build b/meson.build index 44cecaa..500c12c 100644 --- a/meson.build +++ b/meson.build @@ -36,6 +36,7 @@ raven_source_files = [ './src/ColumnLayout.cpp', './src/BoxLayout.cpp', './src/Label.cpp', + './src/ListView.cpp', ] raven_header_files = [ diff --git a/src/ColumnLayout.cpp b/src/ColumnLayout.cpp index f547fed..45d3f89 100644 --- a/src/ColumnLayout.cpp +++ b/src/ColumnLayout.cpp @@ -20,7 +20,7 @@ void VerticalBoxLayout::run() { } if (child->rect().width() > max_width_so_far) { - max_width_so_far = child->rect().height(); + max_width_so_far = child->rect().width(); } requested_height += child->rect().height() + m_margin; diff --git a/src/ListView.cpp b/src/ListView.cpp new file mode 100644 index 0000000..1a840c6 --- /dev/null +++ b/src/ListView.cpp @@ -0,0 +1,93 @@ +#include "ListView.hpp" +#include "pango/pango-layout.h" +#include "src/Painter.hpp" + +namespace Raven { + +void ListView::on_init() { + set_did_init(true); + set_style_pure(&flat_listview_style); + reflow(); +} + +void ListView::elements_updated() { + reflow(); +} + +void ListView::set_active_element(unsigned int active_element) { + if (active_element < 0 || active_element >= elements.size()) { + return; + } + m_active_element = active_element; + if (on_selection) { + on_selection(active_element, elements[active_element]); + } + repaint(); +} + +void ListView::on_mouse_button(MouseButtonEvent &event) { + if (!rect().contains_point(event.point())) { + return; + } + + if (event.was_left_button_pressed()) { + for (unsigned int i = 0; i < elements.size(); i++) { + auto element = elements[i]; + double item_y = m_item_height * i; + double event_y = event.point().y() + m_scroll; + + if (event_y > item_y && event_y < item_y + m_item_height) { + set_active_element(i); + break; + } + } + } else if (event.did_scroll_down()) { + m_scroll += m_scroll_step; + repaint(); + } else if (event.did_scroll_up()) { + m_scroll -= m_scroll_step; + repaint(); + } +} + +void ListView::on_paint() { + for (unsigned int i = 0; i < elements.size(); i++) { + auto element = elements[i]; + double y_pos = m_item_height * i; + double y = y_pos - m_scroll; + + /* ensure the item is visible before painting */ + double visible_extents_start = rect().y() + m_scroll; + double visible_extents_end = rect().y() + rect().height() + m_scroll; + if ((y_pos + m_item_height) < visible_extents_start) { + // This item is out of view, however, we cannot exit the loop here as there are candidates for painting ahead. + continue; + } else if (y_pos > visible_extents_end) { + // The item is out of view and we also don't have any further candidates for painting. + break; + } + + /* paint: background */ + { + if (m_active_element == i) { + painter()->source_rgb(style()->background_active()); + } else { + painter()->source_rgb(style()->background_norm()); + } + painter()->cairo()->rectangle(0, y, item_width(), item_height()); + painter()->fill(); + } + + /* paint: text */ + { + painter()->cairo()->move_to(0, y); + painter()->source_rgb(style()->foreground()); + + Box item_geometry = {0, 0, item_width(), item_height() }; + painter()->text(item_geometry, element, PaintTextAlign::Left, PangoEllipsizeMode::PANGO_ELLIPSIZE_END, style()->font_description()); + painter()->fill(); + } + } +} + +} diff --git a/src/ListView.hpp b/src/ListView.hpp new file mode 100644 index 0000000..04c6990 --- /dev/null +++ b/src/ListView.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "src/Widget.hpp" + +namespace Raven { + +class ListView : public Raven::Widget { +public: + ListView() + : Raven::Widget() {} + + std::vector elements; + + void elements_updated(); + + double item_height() { return m_item_height; } + void set_item_height(double item_height) { m_item_height = item_height; reflow(); } + + double scroll() { return m_scroll; } + void set_scroll(double scroll) { m_scroll = scroll; reflow(); } + + double scroll_step() { return m_scroll_step; } + void set_scroll_step(double scroll_step) { m_scroll_step = scroll_step; } + + double item_width() { return rect().width(); } + + unsigned int active_element() { return m_active_element; } + void set_active_element(unsigned int active_element); + + std::function on_selection { nullptr }; +protected: + void on_paint() override; + void on_mouse_button(MouseButtonEvent &event) override; + void on_init() override; +private: + unsigned int m_active_element { 0 }; + double m_item_height { 26.0 }; + double m_scroll { 0.0 }; + double m_scroll_step { 26.0 }; +}; + +} diff --git a/src/Painter.cpp b/src/Painter.cpp index 98c9096..775b3e6 100644 --- a/src/Painter.cpp +++ b/src/Painter.cpp @@ -72,7 +72,11 @@ void Painter::text(Box &geometry, std::string &text, PaintTextAlign align, Pango x = ((geometry.width() - font_width) / 2); } - m_cairo->move_to(std::floor(x), std::floor(y)); + if (m_cairo->has_current_point()) { + m_cairo->rel_move_to(x, y); + } else { + m_cairo->move_to(x, y); + } pango_cairo_show_layout(m_cairo->cobj(), layout); g_object_unref(layout); diff --git a/src/Styles.cpp b/src/Styles.cpp index 69ec3fb..b6bd79e 100644 --- a/src/Styles.cpp +++ b/src/Styles.cpp @@ -27,7 +27,7 @@ RGB accent2 = RGB(0xb16286); GenericStyle flat_widget_style { pango_font_description_from_string("sans-serif"), - black6, + black1, white3, white3, white3, @@ -38,18 +38,18 @@ GenericStyle flat_widget_style { GenericStyle raised_widget_style { pango_font_description_from_string("sans-serif"), - black6, + black1, white2, white2, white2, - 0.0, + 6.0, true, false }; GenericStyle clear_widget_style { pango_font_description_from_string("sans-serif"), - black6, + black1, white2, white2, white2, @@ -60,22 +60,22 @@ GenericStyle clear_widget_style { GenericStyle flat_button_style { pango_font_description_from_string("sans-serif"), - black6, + black1, white3, white2, white1, - 5.0, + 6.0, true, true }; GenericStyle raised_button_style { pango_font_description_from_string("sans-serif"), - black6, + black1, white2, white1, white0, - 5.0, + 6.0, true, true }; @@ -86,14 +86,14 @@ GenericStyle accent_button_style { accent2, accent1, accent0, - 0.0, + 6.0, true, true }; GenericStyle flat_label_style { pango_font_description_from_string("sans-serif"), - black6, + black1, unused, unused, unused, @@ -102,4 +102,26 @@ GenericStyle flat_label_style { false }; +GenericStyle raised_listview_style { + pango_font_description_from_string("sans-serif"), + black1, + white2, + white0, + accent2, + 6.0, + true, + false +}; + +GenericStyle flat_listview_style { + pango_font_description_from_string("sans-serif"), + black1, + white2, + white0, + accent2, + 6.0, + true, + false +}; + } diff --git a/src/Styles.hpp b/src/Styles.hpp index 1b55765..e6b652d 100644 --- a/src/Styles.hpp +++ b/src/Styles.hpp @@ -31,5 +31,7 @@ extern GenericStyle flat_button_style; extern GenericStyle raised_button_style; extern GenericStyle accent_button_style; extern GenericStyle flat_label_style; +extern GenericStyle flat_listview_style; +extern GenericStyle raised_listview_style; } diff --git a/src/Widget.cpp b/src/Widget.cpp index d0fdc35..b55f4a1 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -194,16 +194,16 @@ void Widget::handle_repaint_rect(RepaintRectEvent &event) { void Widget::handle_relayout_subtree(RelayoutSubtreeEvent &event) { on_layout(); // hack - if (m_layout) { - m_layout->run(); - } - m_window_relative = compute_window_relative(); for (auto child : m_children) { child->dispatch_event(event); } + if (m_layout) { + m_layout->run(); + } + on_after_layout(); } diff --git a/src/main.cpp b/src/main.cpp index b284dc5..b97faea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,6 @@ #include "Label.hpp" +#include "ColumnLayout.hpp" +#include "ListView.hpp" #include "Window.hpp" #include "Widget.hpp" #include "Button.hpp" @@ -21,31 +23,48 @@ int main() { window.spawn_window(); auto main_widget = window.set_main_widget(); - auto main_layout = main_widget->set_layout(Raven::Direction::Vertical); - main_layout->slot_pixel(30); // top bar + auto main_widget_layout = main_widget->set_layout(Raven::Direction::Horizontal); + main_widget_layout->set_margin(8); + main_widget_layout->set_spacing(8); + main_widget_layout->slot_pixel(250); + main_widget_layout->slot_percent(100.0); - auto top_bar = main_widget->add(); - top_bar->set_style(&Raven::raised_widget_style); - top_bar->set_layout(Raven::Direction::Horizontal); + auto list_view = main_widget->add(); - auto container_widget = main_widget->add(); - container_widget->set_style(&Raven::raised_widget_style); - auto container_widget_layout = container_widget->set_layout(Raven::Direction::Vertical); - container_widget_layout->set_margin(24.0); - container_widget_layout->set_spacing(8.0); + auto content = main_widget->add(); + auto content_layout = content->set_layout(); + content_layout->set_margin(6.0); + content->set_style(&Raven::raised_widget_style); - auto new_button = top_bar->add("add", Raven::Button::Accent); - new_button->on_click = [&window, container_widget]() { - window.queue_microtask([container_widget]() { - container_widget->add("hello"); + auto selected_label = content->add("No selection"); + selected_label->rect().set_max_height(28.0); + + auto next_button = content->add("Next Item", Raven::Button::Accent); + + auto delete_button = content->add("Delete Item"); + delete_button->set_style(&Raven::raised_button_style); + + delete_button->on_click = [list_view, &window]() { + window.queue_microtask([list_view](){ + list_view->elements.erase(list_view->elements.begin() + list_view->active_element()); + list_view->set_active_element(list_view->active_element() - 1); }); }; - auto remove_button = top_bar->add("remove", Raven::Button::Flat); - remove_button->on_click = [container_widget]() { - container_widget->clear_children(); + next_button->on_click = [list_view]() { + list_view->set_active_element(list_view->active_element() + 1); }; + list_view->on_selection = [selected_label](unsigned int index, std::string item) { + selected_label->set_text("You have selected: " + item); + }; + + int i = 1000; + while (i --> 0) { + list_view->elements.push_back("Item " + std::to_string(i)); + } + list_view->elements_updated(); + window.run(true); return 0; }