meltygui 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
"""Color view functions and supporting definitions."""
|
|
2
|
+
from meltygui.core.graphics.gl_state import GLState
|
|
3
|
+
from meltygui.core.core_render import render_func
|
|
4
|
+
from meltygui.state.new_core_model import ColorPickerState
|
|
5
|
+
from meltygui.state.new_core_model import DrawState
|
|
6
|
+
from meltygui.state.new_core_model import TabState
|
|
7
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
8
|
+
import meltygui_imgui as imgui
|
|
9
|
+
import types
|
|
10
|
+
from meltygui.hdr_color import pack_color
|
|
11
|
+
from meltygui.core.runtime.toggles import Tint
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def draw_style_policy_fast(owner, draw_state):
|
|
15
|
+
"""One shared code-host editor, only for the selected effective policy."""
|
|
16
|
+
from meltygui.code.new_converters import code_hosts_for
|
|
17
|
+
from meltygui.code.new_converters import host_code_state
|
|
18
|
+
from meltygui.core.styling.fonts import Font
|
|
19
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
20
|
+
from meltygui.view.text_view import draw_text
|
|
21
|
+
from meltygui.core.styling.color_core import _add_style_policy
|
|
22
|
+
from meltygui.core.styling.color_core import _style_policy_source
|
|
23
|
+
|
|
24
|
+
fields = ("tint_fn", "font_size_fn", "font_weight_fn", "shadow_fn")
|
|
25
|
+
selected = draw_state.misc.get("style_policy_index", 0)
|
|
26
|
+
imgui.push_item_width(180)
|
|
27
|
+
imgui.push_style_color(imgui.COLOR_FRAME_BACKGROUND, 0.0, 0.0, 0.0, 0.0)
|
|
28
|
+
changed, selected = imgui.combo("Parent policy", selected,
|
|
29
|
+
["Tint", "Font size", "Font weight", "Shadow"])
|
|
30
|
+
imgui.pop_style_color()
|
|
31
|
+
imgui.pop_item_width()
|
|
32
|
+
if changed:
|
|
33
|
+
draw_state.misc["style_policy_index"] = selected
|
|
34
|
+
request_render()
|
|
35
|
+
fn, source = _style_policy_source(owner, fields[selected])
|
|
36
|
+
if source is not owner:
|
|
37
|
+
from meltygui.view.header_view import flat_button
|
|
38
|
+
if flat_button("+ Add policy here", draw_state,
|
|
39
|
+
view_id="add_style_policy_" + fields[selected], shadow=False):
|
|
40
|
+
_add_style_policy(owner, fields[selected])
|
|
41
|
+
fn, source = _style_policy_source(owner, fields[selected])
|
|
42
|
+
origin = "default" if source is None else ("this view" if source is owner else str(source.name).split("##")[0])
|
|
43
|
+
imgui.text("From: " + origin)
|
|
44
|
+
imgui.text_disabled("Shared function: edits apply to its users.")
|
|
45
|
+
if not isinstance(fn, types.FunctionType) or fn.__name__ == "<lambda>":
|
|
46
|
+
imgui.text_wrapped("This policy has no standalone function definition. Edit its containing source in Inputs.")
|
|
47
|
+
return
|
|
48
|
+
# Identical shared editor to Inputs; no inspect/getsource/exec in this UI.
|
|
49
|
+
str_host, dict_host = code_hosts_for(fn)
|
|
50
|
+
str_host.notify_on_change(draw_state)
|
|
51
|
+
dict_host.notify_on_change(draw_state)
|
|
52
|
+
text = str_host.get(str_host.value_key)
|
|
53
|
+
if not isinstance(text, str):
|
|
54
|
+
imgui.text_disabled("Loading policy…")
|
|
55
|
+
return
|
|
56
|
+
code_state = host_code_state(str_host)
|
|
57
|
+
changed, updated = draw_text(
|
|
58
|
+
text, name="style_policy_" + fields[selected],
|
|
59
|
+
width=480, height=180, font=Font.JETBRAINS_MONO_13,
|
|
60
|
+
is_tree=False, show_header=False, editable=True,
|
|
61
|
+
code_dict=dict_host._held(),
|
|
62
|
+
jump_to=code_state.address if code_state is not None else None)
|
|
63
|
+
if changed and updated != text:
|
|
64
|
+
str_host[str_host.value_key] = updated
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def draw_style_residuals_fast(owner, draw_state=None):
|
|
68
|
+
"""Popover-only controls; locate reads/writes the source driving the view."""
|
|
69
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
70
|
+
|
|
71
|
+
from meltygui.core.styling.style import Style
|
|
72
|
+
from meltygui.state.core_undo import UndoManager
|
|
73
|
+
|
|
74
|
+
value = owner.locate_style
|
|
75
|
+
attr = "locate_style" if value is not None else "locate_tint"
|
|
76
|
+
if value is None:
|
|
77
|
+
value = owner.locate_tint
|
|
78
|
+
original = value
|
|
79
|
+
if not isinstance(value, Style):
|
|
80
|
+
value = Style(value or (0, 0, 0), absolute=value is not None)
|
|
81
|
+
rgb = list(value[:3])
|
|
82
|
+
metadata = value.__getnewargs_ex__()[1]
|
|
83
|
+
alpha = value[3] if len(value) == 4 else 1.0
|
|
84
|
+
imgui.text("Edit " + attr.removeprefix("locate_") + " at its source")
|
|
85
|
+
imgui.push_style_var(imgui.STYLE_FRAME_BORDERSIZE, 1.0)
|
|
86
|
+
changed, metadata['absolute'] = imgui.checkbox("Absolute values", value.absolute)
|
|
87
|
+
imgui.text_disabled("Signed shifts follow parent policy.")
|
|
88
|
+
imgui.separator()
|
|
89
|
+
# Let the existing background context show through the native controls.
|
|
90
|
+
for slot in (imgui.COLOR_FRAME_BACKGROUND, imgui.COLOR_FRAME_BACKGROUND_HOVERED,
|
|
91
|
+
imgui.COLOR_FRAME_BACKGROUND_ACTIVE):
|
|
92
|
+
imgui.push_style_color(slot, 0.0, 0.0, 0.0, 0.0)
|
|
93
|
+
imgui.push_item_width(120)
|
|
94
|
+
for i, label in enumerate(("Red", "Green", "Blue")):
|
|
95
|
+
edited, rgb[i] = imgui.drag_float(label, rgb[i], change_speed=0.005, format="%.3f")
|
|
96
|
+
changed |= edited
|
|
97
|
+
edited, alpha = imgui.slider_float("Opacity", alpha, 0.0, 1.0, format="%.2f")
|
|
98
|
+
changed |= edited
|
|
99
|
+
imgui.separator()
|
|
100
|
+
for name, label, speed in (("font_size", "Size (px)", 0.1),
|
|
101
|
+
("font_weight", "Weight", 5.0),
|
|
102
|
+
("shadow_offset", "Shadow", 0.1)):
|
|
103
|
+
current = metadata[name]
|
|
104
|
+
edited, enabled = imgui.checkbox("##enable_" + name, current is not None)
|
|
105
|
+
if edited:
|
|
106
|
+
metadata[name] = 0.0 if enabled else None
|
|
107
|
+
changed = True
|
|
108
|
+
imgui.same_line()
|
|
109
|
+
if enabled:
|
|
110
|
+
edited, number = imgui.drag_float(label, metadata[name], change_speed=speed, format="%.2f")
|
|
111
|
+
if edited:
|
|
112
|
+
metadata[name] = number
|
|
113
|
+
changed = True
|
|
114
|
+
else:
|
|
115
|
+
imgui.text_disabled(label + " (inherit)")
|
|
116
|
+
imgui.pop_item_width()
|
|
117
|
+
imgui.pop_style_color(3)
|
|
118
|
+
imgui.pop_style_var()
|
|
119
|
+
imgui.separator()
|
|
120
|
+
imgui.text_disabled("Unchecked = inherit.")
|
|
121
|
+
if changed:
|
|
122
|
+
updated = Style(tuple(rgb) + ((alpha,) if len(value) == 4 or alpha != 1.0 else ()), **metadata)
|
|
123
|
+
setter = lambda new: setattr(owner, attr, new)
|
|
124
|
+
UndoManager.record(owner, original, updated, setter=setter,
|
|
125
|
+
key="header_style_residuals", label="Style residuals")
|
|
126
|
+
setter(updated)
|
|
127
|
+
owner.invalidate()
|
|
128
|
+
request_render()
|
|
129
|
+
if draw_state is not None:
|
|
130
|
+
imgui.separator()
|
|
131
|
+
draw_style_policy_fast(owner, draw_state)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def draw_view_offsets_fast(owner, draw_state=None):
|
|
135
|
+
"""Popover-only rows under the colour tabs: the OWNER view's `bg_offset`
|
|
136
|
+
and `z_offset` (VIEW_OFFSET_ROWS), read through `owner.locate_<param>`
|
|
137
|
+
and written back the same way — set_anywhere picks the source that
|
|
138
|
+
drives the param (a `@window(bg_offset=…)`, a caller kwarg, a `# [ ]`
|
|
139
|
+
comment) and falls back to the owner's own draw_state. Every change is
|
|
140
|
+
a SetterChange on the undo stack (like the colour chip's) and invalidates
|
|
141
|
+
the owner; the wrapper reads both offsets on its next run."""
|
|
142
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
143
|
+
|
|
144
|
+
from meltygui.state.core_undo import UndoManager
|
|
145
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
146
|
+
row_width = 120
|
|
147
|
+
imgui.separator()
|
|
148
|
+
for slot in (imgui.COLOR_FRAME_BACKGROUND, imgui.COLOR_FRAME_BACKGROUND_HOVERED,
|
|
149
|
+
imgui.COLOR_FRAME_BACKGROUND_ACTIVE):
|
|
150
|
+
imgui.push_style_color(slot, 0.0, 0.0, 0.0, 0.0)
|
|
151
|
+
imgui.push_item_width(row_width)
|
|
152
|
+
for param, label, speed in Toggles.ColorPicker.view_offset_rows:
|
|
153
|
+
current = getattr(owner, "locate_" + param)
|
|
154
|
+
current = int(current) if isinstance(current, (int, float)) else 0
|
|
155
|
+
edited, value = imgui.drag_int(f"{label}##view_{param}", current, speed)
|
|
156
|
+
if edited and value != current:
|
|
157
|
+
def setter(new, _owner=owner, _attr="locate_" + param):
|
|
158
|
+
setattr(_owner, _attr, new)
|
|
159
|
+
_owner.invalidate()
|
|
160
|
+
UndoManager.record(owner, current, value, setter=setter,
|
|
161
|
+
key="view_" + param, label=label)
|
|
162
|
+
setter(value)
|
|
163
|
+
request_render()
|
|
164
|
+
imgui.pop_item_width()
|
|
165
|
+
imgui.pop_style_color(3)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@render_func(use_cache=False, show_bg=True, shadow=False, selectable=False, with_header=None)
|
|
169
|
+
def draw_color_picker(input_value, wrap=True, draw_state=None, info=None,
|
|
170
|
+
picker_state: ColorPickerState = None, gl_state: GLState = None, owner=None, **kwargs):
|
|
171
|
+
"""The colour picker popover, three tabs: **Wide** (default) — a
|
|
172
|
+
Display-P3 hue/saturation/value square whose value axis runs on above
|
|
173
|
+
white (`Toggles.HDR.picker_max_stops`), rendered as an fp16 texture so
|
|
174
|
+
every texel is the real HDR/P3 colour, with the sRGB-reachable region
|
|
175
|
+
outlined — **sRGB**, the classic square — and **sRGB+**, the classic
|
|
176
|
+
square kept at its size with a wide-gamut extension to its right
|
|
177
|
+
(`_draw_extended_picker`). All take and return extended-sRGB tuples
|
|
178
|
+
(hdr_color.py), so a colour picked on one tab reads back on the others
|
|
179
|
+
(an out-of-sRGB value shows clipped on the sRGB tab)."""
|
|
180
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
181
|
+
|
|
182
|
+
# The tab strip: draw-list flat_buttons (no wrapper per tab), neutral
|
|
183
|
+
# grey like draw_tabs' untinted strip — the active tab gets the filled
|
|
184
|
+
# rect, the others draw label-only.
|
|
185
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
186
|
+
tab_color = (0.5, 0.5, 0.5)
|
|
187
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
188
|
+
tab_height = 21
|
|
189
|
+
from meltygui.view.header_view import flat_button
|
|
190
|
+
imgui.dummy(0, 2)
|
|
191
|
+
tabs = [("Wide", "wide"), ("sRGB", "srgb"), ("sRGB+", "extended")]
|
|
192
|
+
if owner is not None and Toggles.dynamic_styles:
|
|
193
|
+
tabs.append(("Residuals", "residuals"))
|
|
194
|
+
for label, key in tabs:
|
|
195
|
+
selected = picker_state.tab == key
|
|
196
|
+
if flat_button(label, draw_state, view_id=f"cp_tab_{key}", height=tab_height,
|
|
197
|
+
color=tab_color, factor=1.2, corner_radius=4,
|
|
198
|
+
tint_value=0.35 if selected else 0.15, saturation=0.3,
|
|
199
|
+
alpha=1.0 if selected else 0.0, text_value=1.0,
|
|
200
|
+
event="left_mouse_down"):
|
|
201
|
+
picker_state.tab = key
|
|
202
|
+
request_render()
|
|
203
|
+
imgui.same_line(spacing=4)
|
|
204
|
+
imgui.new_line()
|
|
205
|
+
if picker_state.tab == "residuals" and owner is not None and Toggles.dynamic_styles:
|
|
206
|
+
draw_style_residuals_fast(owner, draw_state)
|
|
207
|
+
return False, input_value
|
|
208
|
+
if picker_state.tab == "extended":
|
|
209
|
+
result = _draw_extended_picker(input_value, draw_state, gl_state, info)
|
|
210
|
+
else:
|
|
211
|
+
# The band's room stays reserved: the square lands at the same pos on
|
|
212
|
+
# every tab.
|
|
213
|
+
imgui.dummy(0, Toggles.ColorPicker.exposure_band_height)
|
|
214
|
+
if picker_state.tab == "srgb":
|
|
215
|
+
result = _draw_srgb_picker(input_value, draw_state, info)
|
|
216
|
+
else:
|
|
217
|
+
result = _draw_wide_picker(input_value, draw_state, gl_state, info)
|
|
218
|
+
if owner is not None:
|
|
219
|
+
# Editing a VIEW's colour: its other paint knobs ride along under
|
|
220
|
+
# the colour tabs (color_picker_height reserves two rows).
|
|
221
|
+
draw_view_offsets_fast(owner, draw_state)
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@render_func(use_cache=True, show_bg=True, selectable=False)
|
|
226
|
+
def draw_tint_context(input_value: DrawState, tab_state: TabState = None, **kwargs):
|
|
227
|
+
from meltygui.view.collection_view import draw_tuple
|
|
228
|
+
from meltygui.core.rendering.parameter_core import anywhere_value
|
|
229
|
+
from meltygui.core.rendering.parameter_core import get_source_for
|
|
230
|
+
from meltygui.core.rendering.parameter_core import set_anywhere
|
|
231
|
+
|
|
232
|
+
if Toggles.debug_set_anywhere:
|
|
233
|
+
source_name = get_source_for("tint", input_value)
|
|
234
|
+
imgui.text(f"Tint source: {source_name}")
|
|
235
|
+
|
|
236
|
+
# The live framework-resolved tint - except mid-round-trip, when the
|
|
237
|
+
# pending UI value shows so the widget doesn't snap back while the
|
|
238
|
+
# write→save→hotswap frames play out (anywhere_value).
|
|
239
|
+
tint_changed, tint_value = draw_tuple(anywhere_value("tint", input_value), name="Tint: set anywhere", height=30,
|
|
240
|
+
width=40,
|
|
241
|
+
show_name=False, show_header=False)
|
|
242
|
+
if tint_changed:
|
|
243
|
+
set_anywhere("tint", tint_value, input_value)
|
|
244
|
+
|
|
245
|
+
return False, None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def color_picker_height(n_channels: int, has_info: bool = False,
|
|
250
|
+
has_owner: bool = False) -> int:
|
|
251
|
+
"""Height of draw_color_picker's popover: tab row + exposure band +
|
|
252
|
+
square + the channel rows + the readout line (+ the info caption) (+ the
|
|
253
|
+
view-offset rows an owner draw_state adds, `draw_view_offsets_fast`)."""
|
|
254
|
+
|
|
255
|
+
return (Toggles.ColorPicker.tabs_height + Toggles.ColorPicker.exposure_band_height + Toggles.ColorPicker.square_size + 14
|
|
256
|
+
+ n_channels * 26 + 26 + (22 if has_info else 0)
|
|
257
|
+
+ (Toggles.ColorPicker.offsets_height if has_owner else 0))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def color_picker_top_offset(gap: int = Toggles.ColorPicker.anchor_gap) -> int:
|
|
261
|
+
"""The popover's y offset below its anchor (the cursor under the
|
|
262
|
+
swatch): the popover HANGS under the swatch, tabs first, and the
|
|
263
|
+
exposure band pushes the square down. It used to return `gap` less the
|
|
264
|
+
band so the window grew upward and the square kept its band-less spot —
|
|
265
|
+
that parked the tab row above the swatch, over the host's header row
|
|
266
|
+
(Lukas 09-10: too high)."""
|
|
267
|
+
return gap
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def color_picker_width() -> int:
|
|
271
|
+
"""Width of draw_color_picker's popover: the square + hue bar row of
|
|
272
|
+
the widest tab (sRGB+, whose extension sits between square and hue bar)
|
|
273
|
+
plus the window margins. One size for every tab — the popover is a
|
|
274
|
+
fixed-size closable window and the tab is the picker's own state."""
|
|
275
|
+
|
|
276
|
+
return 216 + Toggles.ColorPicker.extension_width
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _draw_wide_picker(input_value, draw_state, gl_state, info):
|
|
280
|
+
"""The Wide tab: P3 HSV + exposure (hdr_color.p3_hsv_from_extended). The
|
|
281
|
+
square's X is P3 saturation, its top `picker_top_fraction` is exposure
|
|
282
|
+
(2^max_stops at the top, white at the seam), the rest the classic value
|
|
283
|
+
axis. The sRGB outline is the gamut edge for the current hue
|
|
284
|
+
(hdr_color.srgb_region_outline). `draw_state._cpw_precise` echoes our own
|
|
285
|
+
edits back like the sRGB tab's `_cp_precise`."""
|
|
286
|
+
from meltygui.model.color_model import _wide_marker
|
|
287
|
+
from meltygui.model.color_model import _wide_pick
|
|
288
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
289
|
+
from meltygui.view.control_view import button
|
|
290
|
+
|
|
291
|
+
import meltygui.hdr_color as hdr_color
|
|
292
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
293
|
+
outline_color = (1.0, 1.0, 1.0, 0.8)
|
|
294
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
295
|
+
outline_shadow = (0.0, 0.0, 0.0, 0.6)
|
|
296
|
+
SQ, BAR_W, GAP = Toggles.ColorPicker.square_size, 18, 8
|
|
297
|
+
max_stops = float(Toggles.HDR.picker_max_stops)
|
|
298
|
+
top_fraction = float(Toggles.HDR.picker_top_fraction)
|
|
299
|
+
imgui.dummy(0, 3)
|
|
300
|
+
vals = list(input_value)
|
|
301
|
+
has_alpha = len(vals) >= 4
|
|
302
|
+
r, g, b = float(vals[0]), float(vals[1]), float(vals[2])
|
|
303
|
+
a = float(vals[3]) if has_alpha else 1.0
|
|
304
|
+
in_r, in_g, in_b, in_a = r, g, b, a
|
|
305
|
+
|
|
306
|
+
ECHO_TOL = 0.002
|
|
307
|
+
_prec = getattr(draw_state, '_cpw_precise', None)
|
|
308
|
+
is_echo = _prec is not None and all(abs(pc - c) <= ECHO_TOL for pc, c in zip(_prec[0], (r, g, b, a)))
|
|
309
|
+
if is_echo:
|
|
310
|
+
r, g, b, a = _prec[0]
|
|
311
|
+
h, s, v, exposure = _prec[1]
|
|
312
|
+
else:
|
|
313
|
+
h, s, v, exposure = hdr_color.p3_hsv_from_extended(r, g, b)
|
|
314
|
+
if _prec is not None:
|
|
315
|
+
if s <= 0.0 or v <= 0.0:
|
|
316
|
+
h = _prec[1][0]
|
|
317
|
+
if v <= 0.0:
|
|
318
|
+
s = _prec[1][1]
|
|
319
|
+
|
|
320
|
+
dl = imgui.get_window_draw_list()
|
|
321
|
+
white = pack_color(1, 1, 1, 1)
|
|
322
|
+
black = pack_color(0, 0, 0, 1)
|
|
323
|
+
changed = False
|
|
324
|
+
hsv_changed = False
|
|
325
|
+
|
|
326
|
+
# --- the square: an fp16 texture of the hue's slice (re-baked per hue) ---
|
|
327
|
+
sx0, sy0 = imgui.get_cursor_screen_pos()
|
|
328
|
+
tex = _wide_square_texture(gl_state, h, SQ, top_fraction, max_stops)
|
|
329
|
+
if tex is not None:
|
|
330
|
+
dl.add_image(tex.texture_id, (sx0, sy0), (sx0 + SQ, sy0 + SQ))
|
|
331
|
+
imgui.invisible_button("##wsv", SQ, SQ)
|
|
332
|
+
if imgui.is_item_active():
|
|
333
|
+
mx, my = imgui.get_mouse_pos()
|
|
334
|
+
s, v, exposure = _wide_pick((mx - sx0) / SQ, (my - sy0) / SQ, top_fraction, max_stops)
|
|
335
|
+
hsv_changed = True
|
|
336
|
+
|
|
337
|
+
# --- hue bar: P3 hues at full saturation ---
|
|
338
|
+
imgui.same_line(spacing=GAP)
|
|
339
|
+
hx0, hy0 = imgui.get_cursor_screen_pos()
|
|
340
|
+
for i in range(12):
|
|
341
|
+
t0, t1 = i / 12.0, (i + 1) / 12.0
|
|
342
|
+
c0 = pack_color(*hdr_color.p3(*imgui.color_convert_hsv_to_rgb(t0, 1, 1)), 1)
|
|
343
|
+
c1 = pack_color(*hdr_color.p3(*imgui.color_convert_hsv_to_rgb(t1, 1, 1)), 1)
|
|
344
|
+
dl.add_rect_filled_multicolor(hx0, hy0 + SQ * t0, hx0 + BAR_W, hy0 + SQ * t1, c0, c0, c1, c1)
|
|
345
|
+
imgui.invisible_button("##whue", BAR_W, SQ)
|
|
346
|
+
if imgui.is_item_active():
|
|
347
|
+
h = min(max((imgui.get_mouse_pos()[1] - hy0) / SQ, 0.0), 1.0)
|
|
348
|
+
hsv_changed = True
|
|
349
|
+
|
|
350
|
+
# --- sRGB region outline + the white seam ---
|
|
351
|
+
pts = [(sx0 + px * SQ, sy0 + py * SQ) for px, py in hdr_color.srgb_region_outline(h, top_fraction)]
|
|
352
|
+
dl.add_polyline(pts, pack_color(*outline_shadow), False, 3.0)
|
|
353
|
+
dl.add_polyline(pts, pack_color(*outline_color), False, 1.0)
|
|
354
|
+
seam_y = sy0 + top_fraction * SQ
|
|
355
|
+
dl.add_line(sx0, seam_y, sx0 + SQ, seam_y, pack_color(1, 1, 1, 0.25), 1.0)
|
|
356
|
+
|
|
357
|
+
# --- markers ---
|
|
358
|
+
mx_, my_ = _wide_marker(s, v, exposure, top_fraction, max_stops)
|
|
359
|
+
cx, cy = sx0 + mx_ * SQ, sy0 + my_ * SQ
|
|
360
|
+
dl.add_circle(cx, cy, 6, black, thickness=1.0)
|
|
361
|
+
dl.add_circle(cx, cy, 5, white, thickness=1.5)
|
|
362
|
+
hmy = hy0 + h * SQ
|
|
363
|
+
dl.add_rect(hx0 - 1, hmy - 2, hx0 + BAR_W + 1, hmy + 2, white, thickness=1.5)
|
|
364
|
+
|
|
365
|
+
if hsv_changed:
|
|
366
|
+
r, g, b = hdr_color.extended_from_p3_hsv(h, s, v, exposure)
|
|
367
|
+
changed = True
|
|
368
|
+
|
|
369
|
+
# --- channel rows: extended sRGB, so they read past 1 and below 0 ---
|
|
370
|
+
imgui.dummy(0, 4)
|
|
371
|
+
imgui.push_item_width(SQ + GAP + BAR_W)
|
|
372
|
+
out, edited = [], []
|
|
373
|
+
lo, hi = -1.0, hdr_color.linear_to_srgb(2.0 ** max_stops)
|
|
374
|
+
for lbl, cur in ([("R", r), ("G", g), ("B", b)] + ([("A", a)] if has_alpha else [])):
|
|
375
|
+
imgui.set_next_item_width(draw_state.content_width - 30)
|
|
376
|
+
if lbl == "A":
|
|
377
|
+
ch, nv = imgui.drag_float(f"{lbl}##cpw_{lbl}", cur, 0.004, 0.0, 1.0, "%.3f")
|
|
378
|
+
else:
|
|
379
|
+
ch, nv = imgui.drag_float(f"{lbl}##cpw_{lbl}", cur, 0.006, lo, hi, "%.3f")
|
|
380
|
+
if ch:
|
|
381
|
+
changed = True
|
|
382
|
+
edited.append(ch)
|
|
383
|
+
out.append(nv if ch else cur)
|
|
384
|
+
imgui.dummy(0, 1)
|
|
385
|
+
imgui.pop_item_width()
|
|
386
|
+
r, g, b = out[0], out[1], out[2]
|
|
387
|
+
if has_alpha:
|
|
388
|
+
a = out[3]
|
|
389
|
+
|
|
390
|
+
# --- readout: exposure + gamut ---
|
|
391
|
+
# Remove the colour: returns None (the tuple popover unsets its tuple).
|
|
392
|
+
if button("\uf1f8", tint=(1, 0, 0, 0.5), height=21, shadow=True, use_cache=True,
|
|
393
|
+
name="delete_color##", text_value=1.6)[0]:
|
|
394
|
+
request_render()
|
|
395
|
+
return True, None
|
|
396
|
+
imgui.same_line()
|
|
397
|
+
gamut = "sRGB" if all(0.0 <= c <= 1.0 for c in (r, g, b)) else ("P3" if exposure <= 1.0 else "P3 HDR")
|
|
398
|
+
imgui.text_colored(f"{exposure:.2f}× white · {gamut}", *Tint.subtle_text())
|
|
399
|
+
if info:
|
|
400
|
+
imgui.dummy(0, 2)
|
|
401
|
+
imgui.text_colored(str(info), 1.0, 1.0, 1.0, 0.45)
|
|
402
|
+
if changed:
|
|
403
|
+
if has_alpha and not edited[3]:
|
|
404
|
+
a = in_a
|
|
405
|
+
if not hsv_changed:
|
|
406
|
+
if not edited[0]:
|
|
407
|
+
r = in_r
|
|
408
|
+
if not edited[1]:
|
|
409
|
+
g = in_g
|
|
410
|
+
if not edited[2]:
|
|
411
|
+
b = in_b
|
|
412
|
+
nh, ns, nv, ne = hdr_color.p3_hsv_from_extended(r, g, b)
|
|
413
|
+
if ns <= 0.0 or nv <= 0.0:
|
|
414
|
+
nh = h
|
|
415
|
+
if nv <= 0.0:
|
|
416
|
+
ns = s
|
|
417
|
+
h, s, v, exposure = nh, ns, nv, ne
|
|
418
|
+
draw_state._cpw_precise = ((r, g, b, a), (h, s, v, exposure))
|
|
419
|
+
request_render()
|
|
420
|
+
return True, ((r, g, b, a) if has_alpha else (r, g, b))
|
|
421
|
+
return False, input_value
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _wide_square_texture(gl_state, hue, size, top_fraction, max_stops):
|
|
425
|
+
"""The hue slice as an RGBA16F GLTexture, cached on the picker's GLState
|
|
426
|
+
and re-baked when the hue (or the layout toggles) change."""
|
|
427
|
+
if gl_state is None:
|
|
428
|
+
return None
|
|
429
|
+
import meltygui.hdr_color as hdr_color
|
|
430
|
+
import OpenGL.GL as gl
|
|
431
|
+
from meltygui.core.graphics.gl_state import GLTexture
|
|
432
|
+
from meltygui.core.graphics.gl_state import _scalar
|
|
433
|
+
|
|
434
|
+
def create():
|
|
435
|
+
data = hdr_color.wide_square_linear(hue, size, top_fraction, max_stops)
|
|
436
|
+
tex_id = _scalar(gl.glGenTextures(1))
|
|
437
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, tex_id)
|
|
438
|
+
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA16F, size, size, 0, gl.GL_RGBA, gl.GL_FLOAT, data)
|
|
439
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
|
440
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
|
441
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
|
442
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
|
443
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
|
444
|
+
return GLTexture(tex_id, gl.GL_TEXTURE_2D, (size, size), gl.GL_RGBA16F)
|
|
445
|
+
|
|
446
|
+
return gl_state.get("wide_square", create, lambda t: gl.glDeleteTextures([t.texture_id]),
|
|
447
|
+
deps=(round(float(hue), 4), int(size), round(top_fraction, 4), round(max_stops, 4)))
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _draw_extended_picker(input_value, draw_state, gl_state, info):
|
|
451
|
+
"""The sRGB+ tab: the classic sRGB HSV square at its usual size and
|
|
452
|
+
place, extended on two sides — to its RIGHT a PICKER_EXTENSION-wide
|
|
453
|
+
strip that carries every row on past the sRGB gamut edge into Display
|
|
454
|
+
P3 (colour-continuous across the seam, the top row ending in the pure
|
|
455
|
+
P3 primary), and ABOVE square and strip a PICKER_EXPOSURE_BAND-tall
|
|
456
|
+
exposure band that lifts the top row from white at the seam to
|
|
457
|
+
2^Toggles.HDR.picker_max_stops at the top. The whole area is one fp16
|
|
458
|
+
texture per hue (hdr_color.srgb_plus_linear) so every texel is the real
|
|
459
|
+
wide / HDR colour. One drag runs across all of it (_srgb_plus_pick):
|
|
460
|
+
inside the square the classic (s, v); past the right seam s = 1 and x
|
|
461
|
+
is the depth into P3; above the top seam v = 1 and the height is
|
|
462
|
+
exposure. The hue bar is sRGB hue (the square's). Below black there is
|
|
463
|
+
nothing to extend into, so the square's bottom stays the bottom.
|
|
464
|
+
`draw_state._cpx_precise` echoes our own edits back like the other
|
|
465
|
+
tabs' caches; `_cpx_coords` memoizes the inverse for an external value
|
|
466
|
+
(a bisection, not a per-frame cost)."""
|
|
467
|
+
from meltygui.model.color_model import _srgb_plus_marker
|
|
468
|
+
from meltygui.model.color_model import _srgb_plus_pick
|
|
469
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
470
|
+
from meltygui.view.control_view import button
|
|
471
|
+
|
|
472
|
+
import meltygui.hdr_color as hdr_color
|
|
473
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
474
|
+
seam_color = (1.0, 1.0, 1.0, 0.35)
|
|
475
|
+
SQ, EXT, BAND, BAR_W, GAP = Toggles.ColorPicker.square_size, Toggles.ColorPicker.extension_width, Toggles.ColorPicker.exposure_band_height, 18, 8
|
|
476
|
+
max_stops = float(Toggles.HDR.picker_max_stops)
|
|
477
|
+
imgui.dummy(0, 3)
|
|
478
|
+
vals = list(input_value)
|
|
479
|
+
has_alpha = len(vals) >= 4
|
|
480
|
+
r, g, b = float(vals[0]), float(vals[1]), float(vals[2])
|
|
481
|
+
a = float(vals[3]) if has_alpha else 1.0
|
|
482
|
+
in_r, in_g, in_b, in_a = r, g, b, a
|
|
483
|
+
|
|
484
|
+
ECHO_TOL = 0.002
|
|
485
|
+
_prec = getattr(draw_state, '_cpx_precise', None)
|
|
486
|
+
is_echo = _prec is not None and all(abs(pc - c) <= ECHO_TOL for pc, c in zip(_prec[0], (r, g, b, a)))
|
|
487
|
+
if is_echo:
|
|
488
|
+
r, g, b, a = _prec[0]
|
|
489
|
+
h, s, v, x, exposure = _prec[1]
|
|
490
|
+
else:
|
|
491
|
+
memo = getattr(draw_state, '_cpx_coords', None)
|
|
492
|
+
if memo is not None and memo[0] == (r, g, b):
|
|
493
|
+
h, s, v, x, exposure = memo[1]
|
|
494
|
+
else:
|
|
495
|
+
hint = _prec[1][0] if _prec is not None else None
|
|
496
|
+
h, s, v, x, exposure = hdr_color.srgb_extension_coords(r, g, b, hue_hint=hint)
|
|
497
|
+
if _prec is not None:
|
|
498
|
+
if s <= 0.0 or v <= 0.0:
|
|
499
|
+
h = _prec[1][0]
|
|
500
|
+
if v <= 0.0:
|
|
501
|
+
s = _prec[1][1]
|
|
502
|
+
draw_state._cpx_coords = ((r, g, b), (h, s, v, x, exposure))
|
|
503
|
+
|
|
504
|
+
dl = imgui.get_window_draw_list()
|
|
505
|
+
white = pack_color(1, 1, 1, 1)
|
|
506
|
+
black = pack_color(0, 0, 0, 1)
|
|
507
|
+
changed = False
|
|
508
|
+
hsv_changed = False
|
|
509
|
+
|
|
510
|
+
# --- the picking area: band over square + strip, the texture ---
|
|
511
|
+
ax0, ay0 = imgui.get_cursor_screen_pos() # the area's top-left
|
|
512
|
+
sx0, sy0 = ax0, ay0 + BAND # the square's top-left
|
|
513
|
+
tex = _srgb_plus_texture(gl_state, h, SQ, EXT, BAND, max_stops)
|
|
514
|
+
if tex is not None:
|
|
515
|
+
dl.add_image(tex.texture_id, (ax0, ay0), (ax0 + SQ + EXT, ay0 + BAND + SQ))
|
|
516
|
+
seam = pack_color(*seam_color)
|
|
517
|
+
dl.add_line(sx0 + SQ, ay0, sx0 + SQ, sy0 + SQ, seam, 1.0) # sRGB | P3
|
|
518
|
+
dl.add_line(ax0, sy0, ax0 + SQ + EXT, sy0, seam, 1.0) # exposure | SDR
|
|
519
|
+
imgui.invisible_button("##xsv", SQ + EXT, BAND + SQ)
|
|
520
|
+
if imgui.is_item_active():
|
|
521
|
+
mx, my = imgui.get_mouse_pos()
|
|
522
|
+
s, x, v, exposure = _srgb_plus_pick(mx - sx0, my - sy0, SQ, EXT, BAND, max_stops)
|
|
523
|
+
hsv_changed = True
|
|
524
|
+
|
|
525
|
+
# --- hue bar: sRGB hue (the square's axis), the full height ---
|
|
526
|
+
imgui.same_line(spacing=GAP)
|
|
527
|
+
hx0, hy0 = imgui.get_cursor_screen_pos()
|
|
528
|
+
bar_h = BAND + SQ
|
|
529
|
+
for i in range(6):
|
|
530
|
+
t0, t1 = i / 6.0, (i + 1) / 6.0
|
|
531
|
+
c0 = pack_color(*imgui.color_convert_hsv_to_rgb(t0, 1, 1), 1)
|
|
532
|
+
c1 = pack_color(*imgui.color_convert_hsv_to_rgb(t1, 1, 1), 1)
|
|
533
|
+
dl.add_rect_filled_multicolor(hx0, hy0 + bar_h * t0, hx0 + BAR_W, hy0 + bar_h * t1, c0, c0, c1, c1)
|
|
534
|
+
imgui.invisible_button("##xhue", BAR_W, bar_h)
|
|
535
|
+
if imgui.is_item_active():
|
|
536
|
+
h = min(max((imgui.get_mouse_pos()[1] - hy0) / bar_h, 0.0), 1.0)
|
|
537
|
+
hsv_changed = True
|
|
538
|
+
|
|
539
|
+
# --- markers ---
|
|
540
|
+
px, py = _srgb_plus_marker(s, x, v, exposure, SQ, EXT, BAND, max_stops)
|
|
541
|
+
cx, cy = sx0 + px, sy0 + py
|
|
542
|
+
dl.add_circle(cx, cy, 6, black, thickness=1.0)
|
|
543
|
+
dl.add_circle(cx, cy, 5, white, thickness=1.5)
|
|
544
|
+
hmy = hy0 + h * bar_h
|
|
545
|
+
dl.add_rect(hx0 - 1, hmy - 2, hx0 + BAR_W + 1, hmy + 2, white, thickness=1.5)
|
|
546
|
+
|
|
547
|
+
if hsv_changed:
|
|
548
|
+
r, g, b = hdr_color.extended_from_srgb_extension(h, s, v, x, exposure)
|
|
549
|
+
changed = True
|
|
550
|
+
|
|
551
|
+
# --- channel rows: extended sRGB, so they read past 1 and below 0 ---
|
|
552
|
+
imgui.dummy(0, 4)
|
|
553
|
+
imgui.push_item_width(SQ + EXT + GAP + BAR_W)
|
|
554
|
+
out, edited = [], []
|
|
555
|
+
lo, hi = -1.0, hdr_color.linear_to_srgb(2.0 ** max_stops)
|
|
556
|
+
for lbl, cur in ([("R", r), ("G", g), ("B", b)] + ([("A", a)] if has_alpha else [])):
|
|
557
|
+
imgui.set_next_item_width(draw_state.content_width - 30)
|
|
558
|
+
if lbl == "A":
|
|
559
|
+
ch, nv = imgui.drag_float(f"{lbl}##cpx_{lbl}", cur, 0.004, 0.0, 1.0, "%.3f")
|
|
560
|
+
else:
|
|
561
|
+
ch, nv = imgui.drag_float(f"{lbl}##cpx_{lbl}", cur, 0.006, lo, hi, "%.3f")
|
|
562
|
+
if ch:
|
|
563
|
+
changed = True
|
|
564
|
+
edited.append(ch)
|
|
565
|
+
out.append(nv if ch else cur)
|
|
566
|
+
imgui.dummy(0, 1)
|
|
567
|
+
imgui.pop_item_width()
|
|
568
|
+
r, g, b = out[0], out[1], out[2]
|
|
569
|
+
if has_alpha:
|
|
570
|
+
a = out[3]
|
|
571
|
+
|
|
572
|
+
# --- readout: hex inside sRGB, else the gamut + exposure ---
|
|
573
|
+
# Remove the colour: return None (the tuple popover unsets the value).
|
|
574
|
+
if button("\uf1f8", tint=(1, 0, 0, 0.5), height=21, shadow=True, use_cache=True,
|
|
575
|
+
name="delete_color##", text_value=1.6)[0]:
|
|
576
|
+
request_render()
|
|
577
|
+
return True, None
|
|
578
|
+
imgui.same_line()
|
|
579
|
+
if all(0.0 <= c <= 1.0 for c in (r, g, b)):
|
|
580
|
+
ri, gi, bi = (int(round(c * 255)) for c in (r, g, b))
|
|
581
|
+
readout = (f"#{ri:02x}{gi:02x}{bi:02x}{int(round(a * 255)):02x}"
|
|
582
|
+
if has_alpha else f"#{ri:02x}{gi:02x}{bi:02x}")
|
|
583
|
+
else:
|
|
584
|
+
gamut = "sRGB" if x <= 0.0 else "P3"
|
|
585
|
+
readout = f"{exposure:.2f}× white · " + (gamut if exposure <= 1.0 else gamut + " HDR")
|
|
586
|
+
imgui.text_colored(readout, *Tint.subtle_text())
|
|
587
|
+
if info:
|
|
588
|
+
imgui.dummy(0, 2)
|
|
589
|
+
imgui.text_colored(str(info), 1.0, 1.0, 1.0, 0.45)
|
|
590
|
+
if changed:
|
|
591
|
+
if has_alpha and not edited[3]:
|
|
592
|
+
a = in_a
|
|
593
|
+
if not hsv_changed:
|
|
594
|
+
if not edited[0]:
|
|
595
|
+
r = in_r
|
|
596
|
+
if not edited[1]:
|
|
597
|
+
g = in_g
|
|
598
|
+
if not edited[2]:
|
|
599
|
+
b = in_b
|
|
600
|
+
nh, ns, nv, nx, ne = hdr_color.srgb_extension_coords(r, g, b, hue_hint=h)
|
|
601
|
+
if ns <= 0.0 or nv <= 0.0:
|
|
602
|
+
nh = h
|
|
603
|
+
if nv <= 0.0:
|
|
604
|
+
ns = s
|
|
605
|
+
h, s, v, x, exposure = nh, ns, nv, nx, ne
|
|
606
|
+
draw_state._cpx_precise = ((r, g, b, a), (h, s, v, x, exposure))
|
|
607
|
+
request_render()
|
|
608
|
+
return True, ((r, g, b, a) if has_alpha else (r, g, b))
|
|
609
|
+
return False, input_value
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def _srgb_plus_texture(gl_state, hue, square, ext, band, max_stops):
|
|
613
|
+
"""The sRGB+ picking area (band + square + strip) as an RGBA16F
|
|
614
|
+
GLTexture, cached on the picker's GLState and re-baked when the hue (or
|
|
615
|
+
the layout) changes."""
|
|
616
|
+
if gl_state is None:
|
|
617
|
+
return None
|
|
618
|
+
import meltygui.hdr_color as hdr_color
|
|
619
|
+
import OpenGL.GL as gl
|
|
620
|
+
from meltygui.core.graphics.gl_state import GLTexture
|
|
621
|
+
from meltygui.core.graphics.gl_state import _scalar
|
|
622
|
+
cols, rows = int(square + ext), int(band + square)
|
|
623
|
+
|
|
624
|
+
def create():
|
|
625
|
+
data = hdr_color.srgb_plus_linear(hue, square, ext, band, max_stops)
|
|
626
|
+
tex_id = _scalar(gl.glGenTextures(1))
|
|
627
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, tex_id)
|
|
628
|
+
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA16F, cols, rows, 0, gl.GL_RGBA, gl.GL_FLOAT, data)
|
|
629
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
|
630
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
|
631
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
|
632
|
+
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
|
633
|
+
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
|
|
634
|
+
return GLTexture(tex_id, gl.GL_TEXTURE_2D, (cols, rows), gl.GL_RGBA16F)
|
|
635
|
+
|
|
636
|
+
return gl_state.get("srgb_plus", create, lambda t: gl.glDeleteTextures([t.texture_id]),
|
|
637
|
+
deps=(round(float(hue), 4), int(square), int(ext), int(band), round(max_stops, 4)))
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _draw_srgb_picker(input_value, draw_state, info):
|
|
641
|
+
"""The sRGB tab: the classic HSV picker (a saturation/value square plus a
|
|
642
|
+
hue bar. `input_value` is a 3- or 4-float RGB(A) tuple in 0..1; returns
|
|
643
|
+
(changed, new_tuple). HSV is derived from the value each frame and the edit
|
|
644
|
+
written straight back (imgui's own is_item_active tracks the drag), so it
|
|
645
|
+
can be dropped anywhere; draw_tuple wraps it in Mode.POPOVER. The one piece
|
|
646
|
+
of state is `draw_state._cp_precise`: the last full-precision RGBA we
|
|
647
|
+
emitted plus its HSV. RGB→HSV loses hue at black/gray (h collapses to 0)
|
|
648
|
+
and a driven value can echo back quantized (save/parse round trip, %.3f
|
|
649
|
+
drag rounding) — so when the incoming value is just an echo of our own
|
|
650
|
+
edit, we resume from the cache instead of re-deriving."""
|
|
651
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
652
|
+
from meltygui.view.control_view import button
|
|
653
|
+
|
|
654
|
+
imgui.dummy(0, 3)
|
|
655
|
+
vals = list(input_value)
|
|
656
|
+
has_alpha = len(vals) >= 4
|
|
657
|
+
r, g, b = float(vals[0]), float(vals[1]), float(vals[2])
|
|
658
|
+
a = float(vals[3]) if has_alpha else 1.0
|
|
659
|
+
# Exact incoming channels - untouched channels are emitted as these, so
|
|
660
|
+
# an edit to one channel doesn't override the others' cached/rounded
|
|
661
|
+
# working copies.
|
|
662
|
+
in_r, in_g, in_b, in_a = r, g, b, a
|
|
663
|
+
|
|
664
|
+
# Tolerance for "this is our own value coming back"; covers %.3f drag
|
|
665
|
+
# rounding (±0.0005) and 1/255 hex quantization (±0.002).
|
|
666
|
+
ECHO_TOL = 0.002
|
|
667
|
+
_prec = getattr(draw_state, '_cp_precise', None)
|
|
668
|
+
is_echo = False
|
|
669
|
+
if _prec is not None:
|
|
670
|
+
p_rgba, p_hsv = _prec # p_rgba is always stored as a 4-tuple
|
|
671
|
+
is_echo = (abs(p_rgba[0] - r) <= ECHO_TOL and
|
|
672
|
+
abs(p_rgba[1] - g) <= ECHO_TOL and
|
|
673
|
+
abs(p_rgba[2] - b) <= ECHO_TOL and
|
|
674
|
+
abs(p_rgba[3] - a) <= ECHO_TOL)
|
|
675
|
+
|
|
676
|
+
if is_echo:
|
|
677
|
+
# Echo of our own edit - resume the full-precision working value so
|
|
678
|
+
# our SV/hue markers don't jump on quantization noise, and the cached
|
|
679
|
+
# hue survives even when the colour is currently black/gray.
|
|
680
|
+
r, g, b = p_rgba[0], p_rgba[1], p_rgba[2]
|
|
681
|
+
if has_alpha:
|
|
682
|
+
a = p_rgba[3]
|
|
683
|
+
h, s, v = p_hsv
|
|
684
|
+
else:
|
|
685
|
+
h, s, v = imgui.color_convert_rgb_to_hsv(r, g, b)
|
|
686
|
+
if _prec is not None:
|
|
687
|
+
# External change to black/gray: hue (and at black, saturation)
|
|
688
|
+
# are undefined in the new value - keep the cached ones for
|
|
689
|
+
# continuity rather than snapping the markers to red/top-left.
|
|
690
|
+
if s <= 0.0 or v <= 0.0:
|
|
691
|
+
h = _prec[1][0]
|
|
692
|
+
if v <= 0.0:
|
|
693
|
+
s = _prec[1][1]
|
|
694
|
+
|
|
695
|
+
SQ, BAR_W, GAP = 180, 18, 8
|
|
696
|
+
dl = imgui.get_window_draw_list()
|
|
697
|
+
white = pack_color(1, 1, 1, 1)
|
|
698
|
+
black = pack_color(0, 0, 0, 1)
|
|
699
|
+
trans = pack_color(0, 0, 0, 0)
|
|
700
|
+
changed = False
|
|
701
|
+
hsv_changed = False # only convert HSV->RGB when the square/hue is actually changed
|
|
702
|
+
|
|
703
|
+
# Everything renders in NATURAL imgui flow (invisible_button advances the
|
|
704
|
+
# cursor, same_line for the hue bar, plain widgets below). That keeps imgui's
|
|
705
|
+
# content-size tracking honest so the auto-resized popover grows to match the
|
|
706
|
+
# square + channel rows + hex.
|
|
707
|
+
|
|
708
|
+
# --- SV square: white->hue across, transparent->black down ---
|
|
709
|
+
sx0, sy0 = imgui.get_cursor_screen_pos()
|
|
710
|
+
hr, hg, hb = imgui.color_convert_hsv_to_rgb(h, 1.0, 1.0)
|
|
711
|
+
hue = pack_color(hr, hg, hb, 1)
|
|
712
|
+
dl.add_rect_filled_multicolor(sx0, sy0, sx0 + SQ, sy0 + SQ, white, hue, hue, white)
|
|
713
|
+
dl.add_rect_filled_multicolor(sx0, sy0, sx0 + SQ, sy0 + SQ, trans, trans, black, black)
|
|
714
|
+
imgui.invisible_button("##sv", SQ, SQ)
|
|
715
|
+
if imgui.is_item_active():
|
|
716
|
+
mx, my = imgui.get_mouse_pos()
|
|
717
|
+
s = min(max((mx - sx0) / SQ, 0.0), 1.0)
|
|
718
|
+
v = 1.0 - min(max((my - sy0) / SQ, 0.0), 1.0)
|
|
719
|
+
hsv_changed = True
|
|
720
|
+
|
|
721
|
+
# --- Hue bar to its right, 6 gradient segments ---
|
|
722
|
+
imgui.same_line(spacing=GAP)
|
|
723
|
+
hx0, hy0 = imgui.get_cursor_screen_pos()
|
|
724
|
+
for i in range(6):
|
|
725
|
+
t0, t1 = i / 6.0, (i + 1) / 6.0
|
|
726
|
+
r0, g0, b0 = imgui.color_convert_hsv_to_rgb(t0, 1, 1)
|
|
727
|
+
r1, g1, b1 = imgui.color_convert_hsv_to_rgb(t1, 1, 1)
|
|
728
|
+
c0 = pack_color(r0, g0, b0, 1)
|
|
729
|
+
c1 = pack_color(r1, g1, b1, 1)
|
|
730
|
+
dl.add_rect_filled_multicolor(hx0, hy0 + SQ * t0, hx0 + BAR_W, hy0 + SQ * t1, c0, c0, c1, c1)
|
|
731
|
+
imgui.invisible_button("##hue", BAR_W, SQ)
|
|
732
|
+
if imgui.is_item_active():
|
|
733
|
+
h = min(max((imgui.get_mouse_pos()[1] - hy0) / SQ, 0.0), 1.0)
|
|
734
|
+
hsv_changed = True
|
|
735
|
+
|
|
736
|
+
# --- Markers (after input, at the current position) ---
|
|
737
|
+
cx, cy = sx0 + s * SQ, sy0 + (1.0 - v) * SQ
|
|
738
|
+
dl.add_circle(cx, cy, 6, black, thickness=1.0)
|
|
739
|
+
dl.add_circle(cx, cy, 5, white, thickness=1.5)
|
|
740
|
+
hmy = hy0 + h * SQ
|
|
741
|
+
dl.add_rect(hx0 - 1, hmy - 2, hx0 + BAR_W + 1, hmy + 2, white, thickness=1.5)
|
|
742
|
+
|
|
743
|
+
# Fold the SV/hue edit back to RGB only when the square or hue bar was just
|
|
744
|
+
# dragged. Otherwise keep the original input RGB untouched - round-tripping
|
|
745
|
+
# RGB->HSV->RGB every frame accumulates conversion error and feeds it back as
|
|
746
|
+
# next frame's input, which is what made the RGB sliders jitter.
|
|
747
|
+
if hsv_changed:
|
|
748
|
+
r, g, b = imgui.color_convert_hsv_to_rgb(h, s, v)
|
|
749
|
+
changed = True
|
|
750
|
+
|
|
751
|
+
# --- RGBA drag floats (natural flow, below the square row). Dragging a
|
|
752
|
+
# channel edits RGB directly, overriding the HSV-derived value this frame. ---
|
|
753
|
+
imgui.dummy(0, 4)
|
|
754
|
+
imgui.push_item_width(SQ + GAP + BAR_W)
|
|
755
|
+
out, edited = [], []
|
|
756
|
+
for lbl, cur in ([("R", r), ("G", g), ("B", b)] + ([("A", a)] if has_alpha else [])):
|
|
757
|
+
imgui.set_next_item_width(draw_state.content_width - 30)
|
|
758
|
+
ch, nv = imgui.drag_float(f"{lbl}##cp_{lbl}", cur, 0.004, 0.0, 1.0, "%.3f")
|
|
759
|
+
if ch:
|
|
760
|
+
changed = True
|
|
761
|
+
edited.append(ch)
|
|
762
|
+
# Keep `cur` verbatim unless this row was actually dragged - the
|
|
763
|
+
# returned value can differ from format rounding even if untouched.
|
|
764
|
+
out.append(min(max(nv, 0.0), 1.0) if ch else cur)
|
|
765
|
+
imgui.dummy(0, 1)
|
|
766
|
+
imgui.pop_item_width()
|
|
767
|
+
r, g, b = out[0], out[1], out[2]
|
|
768
|
+
if has_alpha:
|
|
769
|
+
a = out[3]
|
|
770
|
+
|
|
771
|
+
# --- Hex code label (#rrggbb, +a with alpha) ---
|
|
772
|
+
ri, gi, bi = (int(round(c * 255)) for c in (r, g, b))
|
|
773
|
+
hex_str = (f"#{ri:02x}{gi:02x}{bi:02x}{int(round(a * 255)):02x}"
|
|
774
|
+
if has_alpha else f"#{ri:02x}{gi:02x}{bi:02x}")
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
if button("\uf1f8", tint=(1, 0, 0, 0.5), height=21, shadow=True, use_cache=True,
|
|
778
|
+
name="delete_color##", text_value=1.6)[0]:
|
|
779
|
+
request_render()
|
|
780
|
+
return True, None
|
|
781
|
+
imgui.same_line()
|
|
782
|
+
imgui.text_colored(hex_str, *Tint.subtle_text())
|
|
783
|
+
if info:
|
|
784
|
+
# General-purpose caption (the tuple widget's info callback) - bottom
|
|
785
|
+
# line, under the hex readout.
|
|
786
|
+
imgui.dummy(0, 2)
|
|
787
|
+
imgui.text_colored(str(info), 1.0, 1.0, 1.0, 0.45)
|
|
788
|
+
if changed:
|
|
789
|
+
# Untouched channels emit the EXACT incoming value - the working
|
|
790
|
+
# copies may be truncated/rounded and must never overwrite precise
|
|
791
|
+
# channels the user didn't edit. An SV/hue drag rewrites RGB
|
|
792
|
+
# wholesale (that edit scope is all three channels); alpha only
|
|
793
|
+
# changes when its own row was dragged.
|
|
794
|
+
if has_alpha and not edited[3]:
|
|
795
|
+
a = in_a
|
|
796
|
+
if not hsv_changed:
|
|
797
|
+
if not edited[0]:
|
|
798
|
+
r = in_r
|
|
799
|
+
if not edited[1]:
|
|
800
|
+
g = in_g
|
|
801
|
+
if not edited[2]:
|
|
802
|
+
b = in_b
|
|
803
|
+
# RGB drags edit the colour directly - refresh the cached HSV,
|
|
804
|
+
# keeping hue/saturation where the new value leaves them undefined.
|
|
805
|
+
nh, ns, nv = imgui.color_convert_rgb_to_hsv(r, g, b)
|
|
806
|
+
if ns <= 0.0 or nv <= 0.0:
|
|
807
|
+
nh = h
|
|
808
|
+
if nv <= 0.0:
|
|
809
|
+
ns = s
|
|
810
|
+
h, s, v = nh, ns, nv
|
|
811
|
+
draw_state._cp_precise = ((r, g, b, a), (h, s, v))
|
|
812
|
+
request_render()
|
|
813
|
+
return True, ((r, g, b, a) if has_alpha else (r, g, b))
|
|
814
|
+
return False, input_value
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def _popover_anchor(draw_state):
|
|
818
|
+
"""The window a header-row popover (draw_tuple_fast's picker) is nested
|
|
819
|
+
under: the host itself when it IS a meltygui window, else the host's
|
|
820
|
+
enclosing window. A popover parented to a plain draw_state inherits its
|
|
821
|
+
`expanded` through abs_closed, so collapsing the host discarded it."""
|
|
822
|
+
if draw_state.closable or draw_state.parent_window is None:
|
|
823
|
+
return draw_state
|
|
824
|
+
return draw_state.parent_window
|