GTreeView.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. #include <LibGUI/GTreeView.h>
  2. #include <LibGUI/GPainter.h>
  3. #include <LibGUI/GScrollBar.h>
  4. //#define DEBUG_ITEM_RECTS
  5. struct Node {
  6. String text;
  7. Node* parent { nullptr };
  8. Vector<Node*> children;
  9. };
  10. class TestModel : public GModel {
  11. public:
  12. static Retained<TestModel> create() { return adopt(*new TestModel); }
  13. TestModel();
  14. virtual int row_count(const GModelIndex& = GModelIndex()) const override;
  15. virtual int column_count(const GModelIndex& = GModelIndex()) const override;
  16. virtual GVariant data(const GModelIndex&, Role = Role::Display) const override;
  17. virtual void update() override;
  18. virtual GModelIndex index(int row, int column = 0, const GModelIndex& parent = GModelIndex()) const override;
  19. virtual ColumnMetadata column_metadata(int) const override{ return { 100 }; }
  20. Node* m_root { nullptr };
  21. };
  22. Node* make_little_tree(int depth, Node* parent)
  23. {
  24. static int next_id = 0;
  25. Node* node = new Node;
  26. node->text = String::format("Node #%d", next_id++);
  27. node->parent = parent;
  28. if (depth)
  29. node->children.append(make_little_tree(depth - 1, node));
  30. return node;
  31. }
  32. GModelIndex TestModel::index(int row, int column, const GModelIndex& parent) const
  33. {
  34. if (!parent.is_valid())
  35. return create_index(row, column, m_root);
  36. auto& node = *(Node*)parent.internal_data();
  37. return create_index(row, column, node.children[row]);
  38. }
  39. TestModel::TestModel()
  40. {
  41. m_root = new Node;
  42. m_root->text = "Root";
  43. m_root->children.append(make_little_tree(3, m_root));
  44. m_root->children.append(make_little_tree(2, m_root));
  45. m_root->children.append(make_little_tree(1, m_root));
  46. }
  47. int TestModel::row_count(const GModelIndex& index) const
  48. {
  49. if (!index.is_valid())
  50. return 1;
  51. auto& node = *(const Node*)index.internal_data();
  52. return node.children.size();
  53. }
  54. int TestModel::column_count(const GModelIndex&) const
  55. {
  56. return 1;
  57. }
  58. void TestModel::update()
  59. {
  60. }
  61. GVariant TestModel::data(const GModelIndex& index, Role role) const
  62. {
  63. if (!index.is_valid())
  64. return { };
  65. auto& node = *(const Node*)index.internal_data();
  66. if (role == GModel::Role::Display) {
  67. return node.text;
  68. }
  69. if (role == GModel::Role::Icon) {
  70. if (node.children.is_empty())
  71. return GIcon::default_icon("filetype-unknown");
  72. return GIcon::default_icon("filetype-folder");
  73. }
  74. return { };
  75. }
  76. struct GTreeView::MetadataForIndex {
  77. bool open { false };
  78. };
  79. GTreeView::MetadataForIndex& GTreeView::ensure_metadata_for_index(const GModelIndex& index) const
  80. {
  81. ASSERT(index.is_valid());
  82. auto it = m_view_metadata.find(index.internal_data());
  83. if (it != m_view_metadata.end())
  84. return *it->value;
  85. auto new_metadata = make<MetadataForIndex>();
  86. auto& new_metadata_ref = *new_metadata;
  87. m_view_metadata.set(index.internal_data(), move(new_metadata));
  88. return new_metadata_ref;
  89. }
  90. GTreeView::GTreeView(GWidget* parent)
  91. : GAbstractView(parent)
  92. {
  93. set_frame_shape(GFrame::Shape::Container);
  94. set_frame_shadow(GFrame::Shadow::Sunken);
  95. set_frame_thickness(2);
  96. set_model(TestModel::create());
  97. m_expand_bitmap = GraphicsBitmap::load_from_file("/res/icons/treeview-expand.png");
  98. m_collapse_bitmap = GraphicsBitmap::load_from_file("/res/icons/treeview-collapse.png");
  99. }
  100. GTreeView::~GTreeView()
  101. {
  102. }
  103. GModelIndex GTreeView::index_at_content_position(const Point& position, bool& is_toggle) const
  104. {
  105. is_toggle = false;
  106. if (!model())
  107. return { };
  108. GModelIndex result;
  109. traverse_in_paint_order([&] (const GModelIndex& index, const Rect& rect, const Rect& toggle_rect, int) {
  110. if (rect.contains(position)) {
  111. result = index;
  112. return IterationDecision::Abort;
  113. }
  114. if (toggle_rect.contains(position)) {
  115. result = index;
  116. is_toggle = true;
  117. return IterationDecision::Abort;
  118. }
  119. return IterationDecision::Continue;
  120. });
  121. return result;
  122. }
  123. void GTreeView::mousedown_event(GMouseEvent& event)
  124. {
  125. if (!model())
  126. return;
  127. auto& model = *this->model();
  128. auto adjusted_position = event.position().translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
  129. bool is_toggle;
  130. auto index = index_at_content_position(adjusted_position, is_toggle);
  131. if (!index.is_valid())
  132. return;
  133. if (model.selected_index() != index) {
  134. model.set_selected_index(index);
  135. update();
  136. }
  137. if (is_toggle && model.row_count(index)) {
  138. auto& metadata = ensure_metadata_for_index(index);
  139. metadata.open = !metadata.open;
  140. update();
  141. }
  142. }
  143. template<typename Callback>
  144. void GTreeView::traverse_in_paint_order(Callback callback) const
  145. {
  146. ASSERT(model());
  147. auto& model = *this->model();
  148. int indent_level = 0;
  149. int y_offset = 0;
  150. auto visible_content_rect = this->visible_content_rect();
  151. Function<IterationDecision(const GModelIndex&)> traverse_index = [&] (const GModelIndex& index) {
  152. int row_count_at_index = model.row_count(index);
  153. if (index.is_valid()) {
  154. auto& metadata = ensure_metadata_for_index(index);
  155. int x_offset = indent_level * indent_width_in_pixels();
  156. auto node_text = model.data(index, GModel::Role::Display).to_string();
  157. Rect rect = {
  158. x_offset, y_offset,
  159. icon_size() + icon_spacing() + text_padding() + font().width(node_text) + text_padding(), item_height()
  160. };
  161. Rect toggle_rect;
  162. if (row_count_at_index > 0) {
  163. int toggle_x = indent_width_in_pixels() * indent_level - icon_size() / 2 - 4;
  164. toggle_rect = { toggle_x, rect.y(), toggle_size(), toggle_size() };
  165. toggle_rect.center_vertically_within(rect);
  166. }
  167. if (rect.intersects(visible_content_rect)) {
  168. if (callback(index, rect, toggle_rect, indent_level) == IterationDecision::Abort)
  169. return IterationDecision::Abort;
  170. }
  171. y_offset += item_height();
  172. // NOTE: Skip traversing children if this index is closed!
  173. if (!metadata.open)
  174. return IterationDecision::Continue;
  175. }
  176. ++indent_level;
  177. int row_count = model.row_count(index);
  178. for (int i = 0; i < row_count; ++i) {
  179. if (traverse_index(model.index(i, 0, index)) == IterationDecision::Abort)
  180. return IterationDecision::Abort;
  181. }
  182. --indent_level;
  183. return IterationDecision::Continue;
  184. };
  185. traverse_index(model.index(0, 0, GModelIndex()));
  186. }
  187. void GTreeView::paint_event(GPaintEvent& event)
  188. {
  189. GFrame::paint_event(event);
  190. GPainter painter(*this);
  191. painter.add_clip_rect(frame_inner_rect());
  192. painter.add_clip_rect(event.rect());
  193. painter.fill_rect(event.rect(), Color::White);
  194. painter.translate(frame_inner_rect().location());
  195. if (!model())
  196. return;
  197. auto& model = *this->model();
  198. traverse_in_paint_order([&] (const GModelIndex& index, const Rect& rect, const Rect& toggle_rect, int indent_level) {
  199. #ifdef DEBUG_ITEM_RECTS
  200. painter.fill_rect(rect, Color::LightGray);
  201. #endif
  202. Color background_color = Color::from_rgb(0xffffff);
  203. Color text_color = Color::from_rgb(0x000000);
  204. Rect icon_rect = { rect.x(), rect.y(), icon_size(), icon_size() };
  205. auto icon = model.data(index, GModel::Role::Icon);
  206. if (icon.is_icon()) {
  207. if (auto* bitmap = icon.as_icon().bitmap_for_size(icon_size()))
  208. painter.blit(icon_rect.location(), *bitmap, bitmap->rect());
  209. }
  210. Rect text_rect = {
  211. icon_rect.right() + 1 + icon_spacing(), rect.y(),
  212. rect.width() - icon_size() - icon_spacing(), rect.height()
  213. };
  214. if (index == model.selected_index()) {
  215. background_color = is_focused() ? Color::from_rgb(0x84351a) : Color::from_rgb(0x606060);
  216. text_color = Color::from_rgb(0xffffff);
  217. painter.fill_rect(text_rect, background_color);
  218. }
  219. auto node_text = model.data(index, GModel::Role::Display).to_string();
  220. painter.draw_text(text_rect, node_text, TextAlignment::Center, text_color);
  221. auto index_at_indent = index;
  222. for (int i = indent_level; i >= 0; --i) {
  223. auto parent_of_index_at_indent = index_at_indent.parent();
  224. bool index_at_indent_is_last_in_parent = index_at_indent.row() == model.row_count(parent_of_index_at_indent) - 1;
  225. Point a { indent_width_in_pixels() * i - icon_size() / 2, rect.y() - 2 };
  226. Point b { a.x(), a.y() + item_height() - 1 };
  227. if (index_at_indent_is_last_in_parent)
  228. b.set_y(rect.center().y());
  229. if (!(i != indent_level && index_at_indent_is_last_in_parent))
  230. painter.draw_line(a, b, Color::MidGray);
  231. if (i == indent_level) {
  232. Point c { a.x(), rect.center().y() };
  233. Point d { c.x() + icon_size() / 2, c.y() };
  234. painter.draw_line(c, d, Color::MidGray);
  235. }
  236. index_at_indent = parent_of_index_at_indent;
  237. }
  238. if (!toggle_rect.is_empty()) {
  239. auto& metadata = ensure_metadata_for_index(index);
  240. if (metadata.open)
  241. painter.blit(toggle_rect.location(), *m_collapse_bitmap, m_collapse_bitmap->rect());
  242. else
  243. painter.blit(toggle_rect.location(), *m_expand_bitmap, m_expand_bitmap->rect());
  244. }
  245. return IterationDecision::Continue;
  246. });
  247. }
  248. void GTreeView::scroll_into_view(const GModelIndex& a_index, Orientation orientation)
  249. {
  250. if (!a_index.is_valid())
  251. return;
  252. Rect found_rect;
  253. traverse_in_paint_order([&] (const GModelIndex& index, const Rect& rect, const Rect&, int) {
  254. if (index == a_index) {
  255. found_rect = rect;
  256. return IterationDecision::Abort;
  257. }
  258. return IterationDecision::Continue;
  259. });
  260. GScrollableWidget::scroll_into_view(found_rect, orientation);
  261. }
  262. void GTreeView::did_update_selection()
  263. {
  264. ASSERT(model());
  265. auto& model = *this->model();
  266. auto index = model.selected_index();
  267. if (!index.is_valid())
  268. return;
  269. ensure_metadata_for_index(index).open = true;
  270. auto parent = index.parent();
  271. while (parent.is_valid()) {
  272. ensure_metadata_for_index(parent).open = true;
  273. parent = parent.parent();
  274. }
  275. }