ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
MenuImgui.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <imgui.h>
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <string>
15 #include <unordered_map>
16 #include <vector>
17 
20 
21 namespace cloudViewer {
22 namespace visualization {
23 namespace gui {
24 
25 static const float EXTRA_PADDING_Y = 1.0f;
26 
27 namespace {
28 
29 std::string CalcShortcutText(KeyName key) {
30  return "";
31  // Dear ImGUI doesn't currently support shortcut keys
32  // if (key == KEY_NONE) {
33  // return "";
34  // }
35  // char k = char(key);
36  // if (k >= 'a' && k <= 'z') {
37  // k -= 32; // uppercase
38  // }
39  // return std::string("Ctrl + ") + char(k);
40 }
41 
42 } // namespace
43 
45  struct MenuItem {
47  std::string name_;
49  std::shared_ptr<MenuImgui> submenu_;
51  nullptr; // so FindMenuItem needn't be a friend
52  bool is_enabled_ = true;
53  bool is_checked_ = false;
54  bool is_separator_ = false;
55  };
56 
57  std::vector<MenuItem> items_;
58  std::unordered_map<int, size_t> id2idx_;
59  bool submenu_visible_ = false;
61 
63  auto it = this->id2idx_.find(item_id);
64  if (it != this->id2idx_.end()) {
65  return &this->items_[it->second];
66  }
67  for (auto &item : this->items_) {
68  if (item.submenu_) {
69  auto *possibility = item.submenu_impl_->FindMenuItem(item_id);
70  if (possibility) {
71  return possibility;
72  }
73  }
74  }
75  return nullptr;
76  }
77 };
78 
79 MenuImgui::MenuImgui() : impl_(new MenuImgui::Impl()) {}
80 
82 
83 void *MenuImgui::GetNativePointer() { return nullptr; }
84 
85 void MenuImgui::AddItem(const char *name,
86  ItemId itemId /*= NO_ITEM*/,
87  KeyName key /*= KEY_NONE*/) {
88  InsertItem(int(impl_->items_.size()), name, itemId, key);
89 }
90 
91 void MenuImgui::AddMenu(const char *name, std::shared_ptr<MenuBase> submenu) {
92  InsertMenu(int(impl_->items_.size()), name, submenu);
93 }
94 
95 void MenuImgui::AddSeparator() { InsertSeparator(int(impl_->items_.size())); }
96 
97 void MenuImgui::InsertItem(int index,
98  const char *name,
99  ItemId item_id /*= NO_ITEM*/,
100  KeyName key /*= KEY_NONE*/) {
101  for (auto &kv : impl_->id2idx_) {
102  if (int(kv.second) >= index) {
103  kv.second += 1;
104  }
105  }
106  impl_->id2idx_[item_id] = impl_->items_.size();
107  impl_->items_.insert(impl_->items_.begin() + index,
108  {item_id, name, key, nullptr});
109 }
110 
111 void MenuImgui::InsertMenu(int index,
112  const char *name,
113  std::shared_ptr<MenuBase> submenu) {
114  for (auto &kv : impl_->id2idx_) {
115  if (int(kv.second) >= index) {
116  kv.second += 1;
117  }
118  }
119  auto imgui_submenu = std::dynamic_pointer_cast<MenuImgui>(submenu);
120  impl_->items_.insert(impl_->items_.begin() + index,
121  {NO_ITEM, name, KEY_NONE, imgui_submenu,
122  imgui_submenu->impl_.get()});
123 }
124 
125 void MenuImgui::InsertSeparator(int index) {
126  for (auto &kv : impl_->id2idx_) {
127  if (int(kv.second) >= index) {
128  kv.second += 1;
129  }
130  }
131  impl_->items_.insert(
132  impl_->items_.begin() + index,
133  {NO_ITEM, "", KEY_NONE, nullptr, nullptr, false, false, true});
134 }
135 
136 int MenuImgui::GetNumberOfItems() const { return int(impl_->items_.size()); }
137 
138 bool MenuImgui::IsEnabled(ItemId item_id) const {
139  auto *item = impl_->FindMenuItem(item_id);
140  if (item) {
141  return item->is_enabled_;
142  }
143  return false;
144 }
145 
146 void MenuImgui::SetEnabled(ItemId item_id, bool enabled) {
147  auto *item = impl_->FindMenuItem(item_id);
148  if (item) {
149  item->is_enabled_ = enabled;
150  }
151 }
152 
153 bool MenuImgui::IsChecked(ItemId item_id) const {
154  auto *item = impl_->FindMenuItem(item_id);
155  if (item) {
156  return item->is_checked_;
157  }
158  return false;
159 }
160 
161 void MenuImgui::SetChecked(ItemId item_id, bool checked) {
162  auto *item = impl_->FindMenuItem(item_id);
163  if (item) {
164  item->is_checked_ = checked;
165  }
166 }
167 
168 int MenuImgui::CalcHeight(const Theme &theme) const {
169  auto em = std::ceil(ImGui::GetTextLineHeight());
170  auto padding = ImGui::GetStyle().FramePadding;
171  return int(std::ceil(em + 2.0f * (padding.y + EXTRA_PADDING_Y)));
172 }
173 
175  return impl_->submenu_visibility_changed_;
176 }
177 
179  bool is_enabled) {
180  ItemId activate_id = NO_ITEM;
181 
182  ImVec2 size;
183  size.x = ImGui::GetIO().DisplaySize.x;
184  size.y = float(CalcHeight(context.theme));
185  auto padding = ImGui::GetStyle().FramePadding;
186  ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
187  ImVec2(padding.x, padding.y + EXTRA_PADDING_Y));
188 
189  impl_->submenu_visibility_changed_ = false;
190  ImGui::BeginMainMenuBar();
191  for (auto &item : impl_->items_) {
192  if (item.submenu_) {
193  bool submenu_visible = item.submenu_impl_->submenu_visible_;
194  auto id = item.submenu_->Draw(context, item.name_.c_str(),
195  is_enabled);
196  if (id >= 0) {
197  activate_id = id;
198  }
199  if (submenu_visible != item.submenu_impl_->submenu_visible_) {
200  impl_->submenu_visibility_changed_ = true;
201  }
202  }
203  }
204 
205  // Before we end the menu bar, draw a one pixel line at the bottom.
206  // This gives a little definition to the end of the menu, otherwise
207  // it just ends and looks a bit odd. This should probably be a pretty
208  // subtle difference from the menubar background.
209  auto y = size.y - 1;
210  ImDrawList *draw_list = ImGui::GetWindowDrawList();
211  draw_list->AddLine(ImVec2(0, y), ImVec2(size.x, y),
212  context.theme.menubar_border_color.ToABGR32(), 1.0f);
213 
214  ImGui::EndMainMenuBar();
215 
216  ImGui::PopStyleVar();
217 
218  return activate_id;
219 }
220 
222  const char *name,
223  bool is_enabled) {
224  ItemId activate_id = NO_ITEM;
225 
226  // The default ImGUI menus are hideous: there is no margin and the items
227  // are spaced way too tightly. However, you can't just add WindowPadding
228  // because then the highlight doesn't extend to the window edge. So we need
229  // to draw the menu item in pieces. First to get the highlight (if
230  // necessary), then draw the actual item inset to the left and right to get
231  // the text and checkbox. Unfortunately, there is no way to get a right
232  // margin without the window padding.
233 
234  auto *font = ImGui::GetFont();
235  int em = int(std::ceil(ImGui::GetTextLineHeight()));
236  int padding = context.theme.default_margin;
237  int name_width = 0, shortcut_width = 0;
238  for (auto &item : impl_->items_) {
239  auto size1 = font->CalcTextSizeA(float(context.theme.font_size), 10000,
240  10000, item.name_.c_str());
241  auto shortcut = CalcShortcutText(item.shortcut_key_);
242  auto size2 = font->CalcTextSizeA(float(context.theme.font_size), 10000,
243  10000, shortcut.c_str());
244  name_width = std::max(name_width, int(std::ceil(size1.x)));
245  shortcut_width = std::max(shortcut_width, int(std::ceil(size2.x)));
246  }
247  int width = padding + name_width + 2 * em + shortcut_width + 2 * em +
248  int(std::ceil(1.5 * em)) + padding; // checkbox
249 
250  ImGui::SetNextWindowContentSize(ImVec2(float(width), 0));
251  ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
252  ImVec2(0, float(context.theme.default_margin)));
253  ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding,
254  float(context.theme.font_size) / 3.0f);
255  ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
256  ImVec2(float(context.theme.default_margin),
257  float(context.theme.default_margin)));
258 
259  if (ImGui::BeginMenu(name, is_enabled)) {
260  impl_->submenu_visible_ = true;
261 
262  for (size_t i = 0; i < impl_->items_.size(); ++i) {
263  auto &item = impl_->items_[i];
264  if (item.is_separator_) {
265  ImGui::Separator();
266  } else if (item.submenu_) {
267  ImGui::SetCursorPosX(float(padding));
268  auto possibility = item.submenu_->Draw(
269  context, item.name_.c_str(), is_enabled);
270  if (possibility != NO_ITEM) {
271  activate_id = possibility;
272  }
273  } else {
274  // Save y position, then draw empty item for the highlight.
275  // Set the enabled flag, in case the real item isn't.
276  auto y = ImGui::GetCursorPosY();
277  if (ImGui::MenuItem("", "", false, item.is_enabled_)) {
278  activate_id = item.id_;
279  }
280  // Restore the y position, and draw the menu item with the
281  // proper margins on top.
282  // Note: can't set width (width - 2 * padding) because
283  // SetNextItemWidth is ignored.
284  ImGui::SetCursorPos(ImVec2(float(padding), y));
285  auto shortcut_text = CalcShortcutText(item.shortcut_key_);
286  ImGui::MenuItem(item.name_.c_str(), shortcut_text.c_str(),
287  item.is_checked_, item.is_enabled_);
288  }
289  }
290  ImGui::EndMenu();
291  } else {
292  impl_->submenu_visible_ = false;
293  }
294 
295  ImGui::PopStyleVar(3);
296 
297  return activate_id;
298 }
299 
300 } // namespace gui
301 } // namespace visualization
302 } // namespace cloudViewer
int width
int size
std::string name
static constexpr ItemId NO_ITEM
Definition: MenuBase.h:29
void SetChecked(ItemId item_id, bool checked) override
Definition: MenuImgui.cpp:161
void AddItem(const char *name, ItemId item_id=NO_ITEM, KeyName key=KEY_NONE) override
Definition: MenuImgui.cpp:85
void AddMenu(const char *name, std::shared_ptr< MenuBase > submenu) override
Definition: MenuImgui.cpp:91
ItemId DrawMenuBar(const DrawContext &context, bool is_enabled) override
Definition: MenuImgui.cpp:178
bool CheckVisibilityChange() const override
Returns true if submenu visibility changed on last call to DrawMenuBar.
Definition: MenuImgui.cpp:174
ItemId Draw(const DrawContext &context, const char *name, bool is_enabled) override
Definition: MenuImgui.cpp:221
void InsertSeparator(int index) override
Definition: MenuImgui.cpp:125
void SetEnabled(ItemId item_id, bool enabled) override
Definition: MenuImgui.cpp:146
bool IsEnabled(ItemId item_id) const override
Definition: MenuImgui.cpp:138
int CalcHeight(const Theme &theme) const override
Definition: MenuImgui.cpp:168
bool IsChecked(ItemId item_id) const override
Definition: MenuImgui.cpp:153
void InsertMenu(int index, const char *name, std::shared_ptr< MenuBase > submenu) override
Definition: MenuImgui.cpp:111
void InsertItem(int index, const char *name, ItemId item_id=NO_ITEM, KeyName key=KEY_NONE) override
Definition: MenuImgui.cpp:97
int max(int a, int b)
Definition: cutil_math.h:48
ImGuiContext * context
Definition: Window.cpp:76
const Theme * theme
Definition: Window.cpp:74
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
static const float EXTRA_PADDING_Y
Definition: MenuImgui.cpp:25
Generic file read and write utility for python interface.
std::unordered_map< int, size_t > id2idx_
Definition: MenuImgui.cpp:58