Repository navigation
Expand file tree
/
Copy pathpy_ui.hpp
More file actions
991 lines (834 loc) · 41.7 KB
/
Copy pathpy_ui.hpp
File metadata and controls
991 lines (834 loc) · 41.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
/* SPDX-FileCopyrightText: 2025 LichtFeld Studio Authors
*
* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "core/export.hpp"
#include "core/modal_request.hpp"
#include "python/python_runtime.hpp"
#include "visualizer/operator/poll_dependency.hpp"
#include <nanobind/nanobind.h>
#include <nanobind/stl/optional.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/tuple.h>
#include <nanobind/stl/vector.h>
#include <cfloat>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace nb = nanobind;
namespace lfs::vis::gui {
struct UIContext;
class IPanel;
struct MenuDropdownContent;
} // namespace lfs::vis::gui
namespace lfs::vis::op {
struct ModalEvent;
class OperatorProperties;
} // namespace lfs::vis::op
namespace Rml {
class ElementDocument;
}
namespace lfs::python {
class LFS_LOCAL_SYMBOL PyOperatorReturnValue {
public:
std::string status;
nb::dict data;
PyOperatorReturnValue(std::string s, nb::dict d)
: status(std::move(s)),
data(std::move(d)) {}
[[nodiscard]] bool finished() const { return status == "FINISHED"; }
[[nodiscard]] bool cancelled() const { return status == "CANCELLED"; }
[[nodiscard]] bool running_modal() const { return status == "RUNNING_MODAL"; }
[[nodiscard]] bool pass_through() const { return status == "PASS_THROUGH"; }
nb::object getattr(const std::string& key) const {
auto py_key = nb::str(key.c_str());
if (data.contains(py_key)) {
return data[py_key];
}
if (key == "data") {
return data;
}
throw nb::attribute_error(("OperatorReturnValue has no attribute '" + key + "'").c_str());
}
nb::dict get_all_data() const { return data; }
};
void register_operator_return_value(nb::module_& m);
enum class LayoutType { Root,
Row,
Column,
Split,
Box,
GridFlow };
struct LayoutState {
bool enabled = true;
bool active = true;
bool alert = false;
float scale_x = 1.0f;
float scale_y = 1.0f;
};
struct LayoutContext {
LayoutType type = LayoutType::Root;
int child_index = 0;
float split_factor = 0.5f;
bool is_first_child = true;
float available_width = 0.0f;
float cursor_start_x = 0.0f;
int grid_columns = 0;
int grid_actual_columns = 0;
bool table_active = false;
LayoutState state;
};
class PyUILayout;
class PySubLayout {
public:
PySubLayout(PyUILayout* parent, LayoutType type, float split_factor = 0.5f,
int grid_columns = 0);
PySubLayout(PySubLayout* parent_sub, LayoutType type, float split_factor = 0.5f,
int grid_columns = 0);
~PySubLayout();
PySubLayout& enter();
void exit();
bool get_enabled() const { return own_state_.enabled; }
void set_enabled(bool v) { own_state_.enabled = v; }
bool get_active() const { return own_state_.active; }
void set_active(bool v) { own_state_.active = v; }
bool get_alert() const { return own_state_.alert; }
void set_alert(bool v) { own_state_.alert = v; }
float get_scale_x() const { return own_state_.scale_x; }
void set_scale_x(float v) { own_state_.scale_x = v; }
float get_scale_y() const { return own_state_.scale_y; }
void set_scale_y(float v) { own_state_.scale_y = v; }
PySubLayout row();
PySubLayout column();
PySubLayout split(float factor = 0.5f);
PySubLayout box();
PySubLayout grid_flow(int columns = 0, bool even_columns = true, bool even_rows = true);
void label(const std::string& text);
bool button(const std::string& label, std::tuple<float, float> size = {0.0f, 0.0f});
bool button_styled(const std::string& label, const std::string& style,
std::tuple<float, float> size = {0.0f, 0.0f});
std::tuple<bool, nb::object> prop(nb::object data, const std::string& prop_id,
std::optional<std::string> text = std::nullopt);
std::tuple<bool, bool> checkbox(const std::string& label, bool value);
std::tuple<bool, float> slider_float(const std::string& label, float v, float min, float max);
std::tuple<bool, int> slider_int(const std::string& label, int v, int min, int max);
std::tuple<bool, float> drag_float(const std::string& label, float v,
float speed = 1.0f, float min = 0.0f, float max = 0.0f);
std::tuple<bool, int> drag_int(const std::string& label, int v,
float speed = 1.0f, int min = 0, int max = 0);
std::tuple<bool, std::string> input_text(const std::string& label, const std::string& value);
std::tuple<bool, int> combo(const std::string& label, int idx,
const std::vector<std::string>& items);
void separator();
void spacing();
void heading(const std::string& text);
bool collapsing_header(const std::string& label, bool default_open = false);
bool tree_node(const std::string& label);
void tree_pop();
void progress_bar(float fraction, const std::string& overlay = "", float width = 0.0f,
float height = 0.0f);
void text_colored(const std::string& text, nb::object color);
void text_wrapped(const std::string& text);
bool begin_table(const std::string& id, int columns);
std::tuple<bool, float> input_float(const std::string& label, float value, float step = 0.0f,
float step_fast = 0.0f, const std::string& format = "%.3f");
std::tuple<bool, int> input_int(const std::string& label, int value, int step = 1, int step_fast = 100);
std::tuple<bool, int> input_int_formatted(const std::string& label, int value, int step = 0, int step_fast = 0);
std::tuple<bool, float> stepper_float(const std::string& label, float value,
const std::vector<float>& steps = {1.0f, 0.1f, 0.01f});
std::tuple<bool, int> radio_button(const std::string& label, int current, int value);
bool small_button(const std::string& label);
bool selectable(const std::string& label, bool selected = false, float height = 0.0f);
std::tuple<bool, std::tuple<float, float, float>> color_edit3(const std::string& label,
std::tuple<float, float, float> color);
void text_disabled(const std::string& text);
std::tuple<bool, int> listbox(const std::string& label, int current_idx,
const std::vector<std::string>& items, int height_items = -1);
void image(uint64_t texture_id, std::tuple<float, float> size,
nb::object tint = nb::none());
bool image_button(const std::string& id, uint64_t texture_id, std::tuple<float, float> size,
nb::object tint = nb::none());
std::tuple<bool, std::string> input_text_with_hint(const std::string& label, const std::string& hint,
const std::string& value);
std::tuple<bool, std::string> input_text_enter(const std::string& label, const std::string& value);
void table_setup_column(const std::string& label, float width = 0.0f) const;
void table_next_row() const;
void table_next_column() const;
void table_headers_row() const;
void end_table() const;
void push_item_width(float width) const;
void pop_item_width() const;
void set_tooltip(const std::string& text) const;
bool is_item_hovered() const;
bool is_item_clicked(int button = 0) const;
void begin_disabled(bool disabled = true) const;
void end_disabled() const;
void same_line(float offset = 0.0f, float spacing = -1.0f) const;
void push_id(const std::string& id) const;
void pop_id() const;
bool begin_child(const std::string& id, std::tuple<float, float> size, bool border = false) const;
void end_child() const;
bool begin_context_menu(const std::string& id = "") const;
void end_context_menu() const;
bool menu_item(const std::string& label, bool enabled = true, bool selected = false) const;
bool begin_menu(const std::string& label) const;
void end_menu() const;
std::tuple<float, float> get_content_region_avail() const;
bool prop_enum(nb::object data, const std::string& prop_id,
const std::string& value, const std::string& text = "");
PyUILayout* parent() const { return parent_; }
void advance_child();
void apply_state();
void pop_per_item_state();
private:
LayoutState effective_state() const;
PyUILayout* parent_;
LayoutState own_state_;
LayoutState inherited_state_;
LayoutType type_;
float split_factor_;
int grid_columns_ = 0;
bool entered_ = false;
int color_push_count_ = 0;
bool disabled_pushed_ = false;
bool font_scale_pushed_ = false;
};
// PanelSpace enum is defined in py_panel_registry.hpp
class PyEvent {
public:
std::string type; // 'MOUSEMOVE', 'LEFTMOUSE', 'RIGHTMOUSE', 'MIDDLEMOUSE', 'KEY_A', etc.
std::string value; // 'PRESS', 'RELEASE', 'CLICK', 'NOTHING'
// Mouse position
double mouse_x = 0.0;
double mouse_y = 0.0;
double mouse_region_x = 0.0;
double mouse_region_y = 0.0;
// Mouse delta (for drag operations)
double delta_x = 0.0;
double delta_y = 0.0;
// Scroll (for WHEELUPMOUSE, WHEELDOWNMOUSE)
double scroll_x = 0.0;
double scroll_y = 0.0;
// Modifier keys
bool shift = false;
bool ctrl = false;
bool alt = false;
// Tablet support (defaults to 1.0 for mouse)
float pressure = 1.0f;
// GUI state - true if mouse is over an application UI surface
bool over_gui = false;
// Raw key code for KEY events
int key_code = 0;
};
// Convert C++ ModalEvent to Python-friendly PyEvent
// (ModalEvent is defined in visualizer/operator/operator_context.hpp)
PyEvent convert_modal_event(const lfs::vis::op::ModalEvent& event);
// Python operator instance registry - for property setting during invocation
nb::object get_python_operator_instance(const std::string& id);
// Keyword arguments of an operator invoke as the properties of a C++ operator, typed by the arguments it
// registered. Raises TypeError for an argument it does not declare.
vis::op::OperatorProperties builtin_operator_properties(const std::string& id, const nb::kwargs& kwargs);
// Forward declarations for scene types (defined in py_scene.hpp)
class PyScene;
class PySceneNode;
// Unified application context - single object for all Python code
// Replaces scattered lf.has_scene(), lf.get_trainer_manager(), etc. calls
class PyAppContext {
public:
// Scene state
bool has_scene() const;
uint64_t scene_generation() const;
// Training state
bool has_trainer() const;
bool is_training() const;
bool is_paused() const;
int iteration() const;
int max_iterations() const;
float loss() const;
// Selection state
bool has_selection() const;
size_t num_gaussians() const;
int selection_submode() const;
int pivot_mode() const;
int transform_space() const;
// Viewport bounds
std::tuple<float, float, float, float> viewport_bounds() const;
bool viewport_valid() const;
nb::object scene() const;
nb::object selected_objects() const;
nb::object active_object() const;
// Returns selected gaussians as a boolean mask tensor
nb::object selected_gaussians() const;
};
// Get the current application context
PyAppContext get_app_context();
// UI layout object passed to draw() for legacy Python plugin compatibility.
class PyUILayout {
public:
enum class Mode : uint8_t {
Legacy,
DrawHook,
};
explicit PyUILayout(int initial_menu_depth = 0) : menu_depth_(initial_menu_depth) {}
explicit PyUILayout(Mode mode, int initial_menu_depth = 0)
: menu_depth_(initial_menu_depth),
mode_(mode) {}
void setCollecting(vis::gui::MenuDropdownContent* target) {
collecting_ = target != nullptr;
collect_target_ = target;
collect_callback_index_ = 0;
}
bool isCollecting() const { return collecting_; }
void setExecuteAtIndex(int index) { execute_at_index_ = index; }
int executeAtIndex() const { return execute_at_index_; }
// Text
void label(const std::string& text);
void label_centered(const std::string& text);
void heading(const std::string& text);
void text_colored(const std::string& text, nb::object color);
void text_colored_centered(const std::string& text, nb::object color);
void text_selectable(const std::string& text, float height = 0);
void text_wrapped(const std::string& text);
void text_disabled(const std::string& text);
void bullet_text(const std::string& text);
// Buttons
bool button(const std::string& label, std::tuple<float, float> size = {0.0f, 0.0f});
bool button_callback(const std::string& label, nb::object callback,
std::tuple<float, float> size = {0.0f, 0.0f});
bool small_button(const std::string& label);
std::tuple<bool, bool> checkbox(const std::string& label, bool value);
std::tuple<bool, int> radio_button(const std::string& label, int current, int value);
// Sliders
std::tuple<bool, float> slider_float(const std::string& label, float value, float min, float max);
std::tuple<bool, int> slider_int(const std::string& label, int value, int min, int max);
std::tuple<bool, std::tuple<float, float>> slider_float2(const std::string& label,
std::tuple<float, float> value,
float min, float max);
std::tuple<bool, std::tuple<float, float, float>> slider_float3(const std::string& label,
std::tuple<float, float, float> value,
float min, float max);
// Drags
std::tuple<bool, float> drag_float(const std::string& label, float value,
float speed = 1.0f, float min = 0.0f, float max = 0.0f);
std::tuple<bool, int> drag_int(const std::string& label, int value,
float speed = 1.0f, int min = 0, int max = 0);
// Input
std::tuple<bool, std::string> input_text(const std::string& label, const std::string& value);
std::tuple<bool, std::string> input_text_with_hint(const std::string& label, const std::string& hint,
const std::string& value);
std::tuple<bool, float> input_float(const std::string& label, float value, float step = 0.0f,
float step_fast = 0.0f, const std::string& format = "%.3f");
std::tuple<bool, int> input_int(const std::string& label, int value, int step = 1, int step_fast = 100);
std::tuple<bool, int> input_int_formatted(const std::string& label, int value, int step = 0, int step_fast = 0);
std::tuple<bool, float> stepper_float(const std::string& label, float value,
const std::vector<float>& steps = {1.0f, 0.1f, 0.01f});
std::tuple<bool, std::string> path_input(const std::string& label, const std::string& value,
bool folder_mode = true,
const std::string& dialog_title = "");
// Color
std::tuple<bool, std::tuple<float, float, float>> color_edit3(const std::string& label,
std::tuple<float, float, float> color);
std::tuple<bool, std::tuple<float, float, float, float>> color_edit4(const std::string& label,
std::tuple<float, float, float, float> color);
std::tuple<bool, std::tuple<float, float, float>> color_picker3(const std::string& label,
std::tuple<float, float, float> color);
bool color_button(const std::string& label, nb::object color,
std::tuple<float, float> size = {0.0f, 0.0f});
// Selection
std::tuple<bool, int> combo(const std::string& label, int current_idx,
const std::vector<std::string>& items);
std::tuple<bool, int> listbox(const std::string& label, int current_idx,
const std::vector<std::string>& items, int height_items = -1);
// Layout
void separator();
void spacing();
void same_line(float offset = 0.0f, float spacing = -1.0f);
void new_line();
void indent(float width = 0.0f);
void unindent(float width = 0.0f);
void set_next_item_width(float width);
// Grouping
void begin_group();
void end_group();
bool collapsing_header(const std::string& label, bool default_open = false);
bool tree_node(const std::string& label);
bool tree_node_ex(const std::string& label, const std::string& flags);
void set_next_item_open(bool is_open);
void tree_pop();
// Tables
bool begin_table(const std::string& id, int columns);
void table_setup_column(const std::string& label, float width = 0.0f);
void end_table();
void table_next_row();
void table_next_column();
bool table_set_column_index(int column);
void table_headers_row();
void table_set_bg_color(int target, nb::object color);
// Styled buttons
bool button_styled(const std::string& label, const std::string& style,
std::tuple<float, float> size = {0.0f, 0.0f});
// Item width stack
void push_item_width(float width);
void pop_item_width();
// Plots
void plot_lines(const std::string& label, const std::vector<float>& values,
float scale_min = FLT_MAX, float scale_max = FLT_MAX,
std::tuple<float, float> size = {0.0f, 0.0f});
// Selectable
bool selectable(const std::string& label, bool selected = false, float height = 0.0f);
// Context menus
bool begin_context_menu(const std::string& id = "");
void end_context_menu();
bool begin_popup(const std::string& id);
void open_popup(const std::string& id);
void end_popup();
bool menu_item(const std::string& label, bool enabled = true, bool selected = false);
bool begin_menu(const std::string& label);
void end_menu();
std::tuple<bool, std::string> input_text_enter(const std::string& label, const std::string& value);
void set_keyboard_focus_here();
bool is_window_focused() const;
bool is_window_hovered() const;
void capture_keyboard_from_app(bool capture = true);
void capture_mouse_from_app(bool capture = true);
void set_scroll_here_y(float center_y_ratio = 0.5f);
std::tuple<float, float> get_cursor_screen_pos() const;
std::tuple<float, float> get_mouse_pos() const;
std::tuple<float, float> get_window_pos() const;
float get_window_width() const;
float get_text_line_height() const;
// Modal popups
bool begin_popup_modal(const std::string& title);
void end_popup_modal();
void close_current_popup();
void set_next_window_pos_center();
void set_next_window_pos_viewport_center(bool always = false);
void set_next_window_focus();
void push_modal_style();
void pop_modal_style();
// Cursor and content region
std::tuple<float, float> get_content_region_avail();
std::tuple<float, float> get_cursor_pos();
void set_cursor_pos_x(float x);
std::tuple<float, float> calc_text_size(const std::string& text);
// Disabled state
void begin_disabled(bool disabled = true);
void end_disabled();
// Images (texture_id is an opaque UI texture handle as uint64)
void image(uint64_t texture_id, std::tuple<float, float> size,
nb::object tint = nb::none());
void image_uv(uint64_t texture_id, std::tuple<float, float> size,
std::tuple<float, float> uv0, std::tuple<float, float> uv1,
nb::object tint = nb::none());
bool image_button(const std::string& id, uint64_t texture_id, std::tuple<float, float> size,
nb::object tint = nb::none());
// Toolbar button (icon with selection state, themed for toolbar use)
bool toolbar_button(const std::string& id, uint64_t texture_id, std::tuple<float, float> size,
bool selected = false, bool disabled = false, const std::string& tooltip = "");
// Drag-drop
bool begin_drag_drop_source();
void set_drag_drop_payload(const std::string& type, const std::string& data);
void end_drag_drop_source();
bool begin_drag_drop_target();
std::optional<std::string> accept_drag_drop_payload(const std::string& type);
void end_drag_drop_target();
// Misc
void progress_bar(float fraction, const std::string& overlay = "", float width = 0.0f,
float height = 0.0f);
void set_tooltip(const std::string& text);
bool is_item_hovered();
bool is_item_clicked(int button = 0);
bool is_item_active();
bool is_mouse_double_clicked(int button = 0);
bool is_mouse_dragging(int button = 0);
float get_mouse_wheel();
std::tuple<float, float> get_mouse_delta();
bool invisible_button(const std::string& id, std::tuple<float, float> size);
void set_cursor_pos(std::tuple<float, float> pos);
// Child windows
bool begin_child(const std::string& id, std::tuple<float, float> size, bool border = false);
void end_child();
// Menu bar
bool begin_menu_bar();
void end_menu_bar();
bool menu_item_toggle(const std::string& label, const std::string& shortcut, bool selected);
bool menu_item_shortcut(const std::string& label, const std::string& shortcut, bool enabled = true);
void push_id(const std::string& id);
void push_id_int(int id);
void pop_id();
// Window control (for floating panels)
// flags: combination of WindowFlags values
bool begin_window(const std::string& title, int flags = 0);
std::tuple<bool, bool> begin_window_closable(const std::string& title, int flags = 0);
void end_window();
void push_window_style();
void pop_window_style();
// Window positioning (for viewport overlays)
void set_next_window_pos(std::tuple<float, float> pos, bool first_use = false);
void set_next_window_size(std::tuple<float, float> size, bool first_use = false);
void set_next_window_pos_centered(bool first_use = false);
void set_next_window_bg_alpha(float alpha);
std::tuple<float, float> get_viewport_pos();
std::tuple<float, float> get_viewport_size();
float get_dpi_scale();
// Cursor
void set_mouse_cursor_hand();
// Style control
void push_style_var_float(const std::string& var, float value);
void push_style_var_vec2(const std::string& var, std::tuple<float, float> value);
void pop_style_var(int count = 1);
void push_style_color(const std::string& col, nb::object color);
void pop_style_color(int count = 1);
// RNA-style property widget (auto-generates from metadata)
// Returns (changed, new_value) - draws widget based on property type
std::tuple<bool, nb::object> prop(nb::object data,
const std::string& prop_id,
std::optional<std::string> text = std::nullopt);
PySubLayout row();
PySubLayout column();
PySubLayout split(float factor = 0.5f);
PySubLayout box();
PySubLayout grid_flow(int columns = 0, bool even_columns = true, bool even_rows = true);
bool prop_enum(nb::object data, const std::string& prop_id,
const std::string& value, const std::string& text = "");
int next_box_id() { return box_id_counter_++; }
int next_grid_id() { return grid_id_counter_++; }
void reset_frame_state() {
box_id_counter_ = 0;
grid_id_counter_ = 0;
}
nb::object operator_(const std::string& operator_id, const std::string& text = "",
const std::string& icon = "");
// Searchable dropdown for selecting from a collection
std::tuple<bool, int> prop_search(nb::object data, const std::string& prop_id,
nb::object search_data, const std::string& search_prop,
const std::string& text = "");
// UIList template for drawing custom lists
// Returns (active_index, list_length)
std::tuple<int, int> template_list(const std::string& list_type_id, const std::string& list_id,
nb::object data, const std::string& prop_id,
nb::object active_data, const std::string& active_prop,
int rows = 5);
// Inline menu reference
void menu(const std::string& menu_id, const std::string& text = "", const std::string& icon = "");
// Panel popover
void popover(const std::string& panel_id, const std::string& text = "", const std::string& icon = "");
// Drawing functions for viewport overlays
void draw_circle(float x, float y, float radius,
nb::object color,
int segments = 32, float thickness = 1.0f);
void draw_circle_filled(float x, float y, float radius,
nb::object color,
int segments = 32);
void draw_rect(float x0, float y0, float x1, float y1,
nb::object color,
float thickness = 1.0f);
void draw_rect_filled(float x0, float y0, float x1, float y1,
nb::object color, bool background = false);
void draw_rect_rounded(float x0, float y0, float x1, float y1,
nb::object color,
float rounding, float thickness = 1.0f, bool background = false);
void draw_rect_rounded_filled(float x0, float y0, float x1, float y1,
nb::object color,
float rounding, bool background = false);
void draw_triangle_filled(float x0, float y0, float x1, float y1, float x2, float y2,
nb::object color, bool background = false);
void draw_line(float x0, float y0, float x1, float y1,
nb::object color,
float thickness = 1.0f);
void draw_polyline(const std::vector<std::tuple<float, float>>& points,
nb::object color,
bool closed = false, float thickness = 1.0f);
void draw_poly_filled(const std::vector<std::tuple<float, float>>& points,
nb::object color);
void draw_text(float x, float y, const std::string& text,
nb::object color, bool background = false);
// Window-scoped drawing (respects z-order)
void draw_window_rect_filled(float x0, float y0, float x1, float y1,
nb::object color);
void draw_window_rect(float x0, float y0, float x1, float y1,
nb::object color, float thickness = 1.0f);
void draw_window_rect_rounded(float x0, float y0, float x1, float y1,
nb::object color,
float rounding, float thickness = 1.0f);
void draw_window_rect_rounded_filled(float x0, float y0, float x1, float y1,
nb::object color,
float rounding);
void draw_window_line(float x0, float y0, float x1, float y1,
nb::object color, float thickness = 1.0f);
void draw_window_text(float x, float y, const std::string& text,
nb::object color);
void draw_window_triangle_filled(float x0, float y0, float x1, float y1, float x2, float y2,
nb::object color);
void crf_curve_preview(const std::string& label, float gamma, float toe, float shoulder,
float gamma_r = 0.0f, float gamma_g = 0.0f, float gamma_b = 0.0f);
std::tuple<bool, std::vector<float>> chromaticity_diagram(
const std::string& label,
float red_x, float red_y, float green_x, float green_y,
float blue_x, float blue_y, float neutral_x, float neutral_y,
float range = 0.5f);
private:
friend class PyUILayoutTemplates;
void warnUnsupportedInDrawHook(const char* method) const;
[[nodiscard]] bool isDrawHook() const { return mode_ == Mode::DrawHook; }
int menu_depth_;
int box_id_counter_ = 0;
int grid_id_counter_ = 0;
bool collecting_ = false;
vis::gui::MenuDropdownContent* collect_target_ = nullptr;
int collect_callback_index_ = 0;
int execute_at_index_ = -1;
Mode mode_ = Mode::Legacy;
std::tuple<float, float> next_window_pos_{0.0f, 0.0f};
std::tuple<float, float> next_window_size_{0.0f, 0.0f};
bool next_window_pos_set_ = false;
bool next_window_size_set_ = false;
std::tuple<float, float> window_pos_{0.0f, 0.0f};
std::tuple<float, float> window_size_{0.0f, 0.0f};
};
using PollDependency = lfs::vis::op::PollDependency;
namespace gui = lfs::vis::gui;
class PyPanelRegistry {
public:
static PyPanelRegistry& instance();
void register_panel(nb::object panel_class);
void unregister_panel(nb::object panel_class);
void unregister_for_module(const std::string& prefix);
void unregister_all();
[[nodiscard]] std::string module_for_panel(const std::string& panel_id) const;
private:
PyPanelRegistry() = default;
~PyPanelRegistry() = default;
PyPanelRegistry(const PyPanelRegistry&) = delete;
PyPanelRegistry& operator=(const PyPanelRegistry&) = delete;
struct RegisteredPanel {
std::shared_ptr<gui::IPanel> adapter;
std::string module_prefix;
};
mutable std::mutex mutex_;
std::unordered_map<std::string, RegisteredPanel> panels_;
};
// Theme palette wrapper (read-only)
struct PyThemePalette {
std::tuple<float, float, float, float> background;
std::tuple<float, float, float, float> surface;
std::tuple<float, float, float, float> surface_bright;
std::tuple<float, float, float, float> primary;
std::tuple<float, float, float, float> primary_dim;
std::tuple<float, float, float, float> secondary;
std::tuple<float, float, float, float> text;
std::tuple<float, float, float, float> text_dim;
std::tuple<float, float, float, float> border;
std::tuple<float, float, float, float> success;
std::tuple<float, float, float, float> warning;
std::tuple<float, float, float, float> error;
std::tuple<float, float, float, float> info;
std::tuple<float, float, float, float> toolbar_background;
std::tuple<float, float, float, float> row_even;
std::tuple<float, float, float, float> row_odd;
std::tuple<float, float, float, float> overlay_border;
std::tuple<float, float, float, float> overlay_icon;
std::tuple<float, float, float, float> overlay_text;
std::tuple<float, float, float, float> overlay_text_dim;
};
// Theme sizes wrapper (read-only)
struct PyThemeSizes {
float window_rounding;
float frame_rounding;
float popup_rounding;
float scrollbar_rounding;
float tab_rounding;
float border_size;
std::tuple<float, float> window_padding;
std::tuple<float, float> frame_padding;
std::tuple<float, float> item_spacing;
float toolbar_button_size;
float toolbar_padding;
float toolbar_spacing;
};
struct PyThemeVignette {
bool enabled;
float intensity;
float radius;
float softness;
};
// Theme wrapper (read-only)
struct PyTheme {
std::string name;
PyThemePalette palette;
PyThemeSizes sizes;
PyThemeVignette vignette;
};
// Get current theme
PyTheme get_current_theme();
// Hook position enum (mirrors ui_hooks.hpp but with Python bindings)
enum class PyHookPosition {
Prepend, // Run before native content
Append // Run after native content
};
// UI Hook registry for Python callbacks
class LFS_LOCAL_SYMBOL PyUIHookRegistry {
public:
static PyUIHookRegistry& instance();
// Register a Python callback for a hook point
void add_hook(const std::string& panel,
const std::string& section,
nb::object callback,
PyHookPosition position = PyHookPosition::Append);
// Remove a specific hook
void remove_hook(const std::string& panel,
const std::string& section,
nb::object callback);
// Clear hooks for a panel/section
void clear_hooks(const std::string& panel, const std::string& section = "");
void clear_hooks_for_module(const std::string& module_prefix);
// Clear all hooks
void clear_all();
// Invoke hooks - called from C++ panels
void invoke(const std::string& panel,
const std::string& section,
PyHookPosition position);
bool invoke_document(const std::string& panel,
const std::string& section,
Rml::ElementDocument* document,
PyHookPosition position);
// Check if hooks exist
bool has_hooks(const std::string& panel, const std::string& section) const;
bool has_hooks(const std::string& panel, const std::string& section, PyHookPosition position) const;
// Get all registered hook points
std::vector<std::string> get_hook_points() const;
private:
PyUIHookRegistry() = default;
~PyUIHookRegistry() = default;
PyUIHookRegistry(const PyUIHookRegistry&) = delete;
PyUIHookRegistry& operator=(const PyUIHookRegistry&) = delete;
struct LFS_LOCAL_SYMBOL HookEntry {
nb::object callback;
PyHookPosition position;
std::string name;
std::string module;
};
mutable std::mutex mutex_;
std::unordered_map<std::string, std::vector<HookEntry>> hooks_;
};
// MenuLocation enum defined in python/python_runtime.hpp
struct LFS_LOCAL_SYMBOL PyMenuClassInfo {
std::string idname;
std::string label;
MenuLocation location;
int order;
nb::object menu_class;
nb::object menu_instance;
};
class LFS_LOCAL_SYMBOL PyMenuRegistry {
public:
static PyMenuRegistry& instance();
void register_menu(nb::object menu_class);
void unregister_menu(nb::object menu_class);
void unregister_all();
void draw_menu_items(MenuLocation location);
bool has_items(MenuLocation location) const;
std::vector<PyMenuClassInfo*> get_menu_bar_entries();
void draw_menu_bar_entry(const std::string& idname);
vis::gui::MenuDropdownContent collect_menu_content(const std::string& idname);
void execute_menu_callback(const std::string& idname, int callback_index);
void sync_from_python() const;
private:
PyMenuRegistry() = default;
~PyMenuRegistry() = default;
PyMenuRegistry(const PyMenuRegistry&) = delete;
PyMenuRegistry& operator=(const PyMenuRegistry&) = delete;
void ensure_synced() const;
mutable std::mutex mutex_;
mutable std::vector<PyMenuClassInfo> menu_classes_;
mutable bool synced_from_python_ = false;
};
class LFS_LOCAL_SYMBOL PyOperatorProperties {
public:
explicit PyOperatorProperties(const std::string& operator_id);
void set_property(const std::string& name, nb::object value);
[[nodiscard]] nb::object get_property(const std::string& name) const;
[[nodiscard]] nb::dict get_properties() const;
[[nodiscard]] const std::string& get_operator_id() const { return operator_id_; }
private:
std::string operator_id_;
nb::dict properties_;
};
// Modal dialog types
enum class ModalDialogType { Confirm,
Input,
Message };
// Message style for visual appearance
enum class MessageStyle { Info,
Warning,
Error };
// Modal dialog info
struct LFS_LOCAL_SYMBOL PyModalDialog {
std::string id;
std::string title;
std::string message;
std::vector<std::string> buttons;
nb::object callback;
std::function<void(const std::string&)> cpp_callback;
ModalDialogType type;
MessageStyle style = MessageStyle::Info;
std::string input_value;
bool is_open = true;
bool needs_open = true;
};
class LFS_LOCAL_SYMBOL PyModalRegistry {
public:
using EnqueueCallback = std::function<void(lfs::core::ModalRequest)>;
static PyModalRegistry& instance();
void set_enqueue_callback(EnqueueCallback cb);
void show_confirm(const std::string& title, const std::string& message,
const std::vector<std::string>& buttons, nb::object callback,
MessageStyle style = MessageStyle::Info);
void show_confirm(const std::string& title, const std::string& message,
const std::vector<std::string>& buttons,
std::function<void(const std::string&)> callback,
MessageStyle style = MessageStyle::Info);
void show_input(const std::string& title, const std::string& message,
const std::string& default_value, nb::object callback);
void show_message(const std::string& title, const std::string& message,
MessageStyle style = MessageStyle::Info);
void show_message(const std::string& title, const std::string& message,
MessageStyle style, nb::object callback);
void draw_modals();
bool has_open_modals() const;
void clear_for_test();
bool can_lock_mutex_for_test() const;
void run_pending_callback_for_test(std::function<void()> callback);
private:
PyModalRegistry() = default;
~PyModalRegistry() = default;
PyModalRegistry(const PyModalRegistry&) = delete;
PyModalRegistry& operator=(const PyModalRegistry&) = delete;
using ModalCallbackAction = std::function<void()>;
mutable std::mutex mutex_;
std::vector<PyModalDialog> modals_;
uint32_t next_id_ = 0;
EnqueueCallback enqueue_cb_;
};
// Register UI classes with nanobind module
void register_ui(nb::module_& m);
// Register unified class API on root module (lf.register_class, lf.unregister_class)
void register_class_api(nb::module_& m);
// Sub-registration functions (called by register_ui)
void register_ui_context(nb::module_& m);
void register_ui_theme(nb::module_& m);
void register_ui_operators(nb::module_& m);
void register_ui_modals(nb::module_& m);
void register_ui_hooks(nb::module_& m);
void register_ui_menus(nb::module_& m);
void register_ui_context_menu(nb::module_& m);
void register_ui_panels(nb::module_& m);
bool unload_panel_plugin(const std::string& panel_id);
void register_rml_im_mode_layout(nb::module_& m);
void register_keymap(nb::module_& m);
[[nodiscard]] std::string rml_src_for_dynamic_texture(uint64_t texture_id, int width, int height);
} // namespace lfs::python