feat(ui): Improve text rendering clarity with 3x font oversampling

This commit is contained in:
2026-03-09 00:13:57 -04:00
parent fde0f29e72
commit 5446a2407c
6 changed files with 277 additions and 40 deletions

View File

@@ -1,6 +1,6 @@
[ai] [ai]
provider = "minimax" provider = "deepseek"
model = "MiniMax-M2.5" model = "deepseek-chat"
temperature = 0.0 temperature = 0.0
max_tokens = 24000 max_tokens = 24000
history_trunc_limit = 900000 history_trunc_limit = 900000
@@ -9,6 +9,11 @@ system_prompt = ""
[projects] [projects]
paths = [ paths = [
"C:/projects/gencpp/gencpp_sloppy.toml", "C:/projects/gencpp/gencpp_sloppy.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_livecontextsim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveaisettingssim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml",
] ]
active = "C:/projects/gencpp/gencpp_sloppy.toml" active = "C:/projects/gencpp/gencpp_sloppy.toml"
@@ -16,6 +21,7 @@ active = "C:/projects/gencpp/gencpp_sloppy.toml"
separate_message_panel = false separate_message_panel = false
separate_response_panel = false separate_response_panel = false
separate_tool_calls_panel = false separate_tool_calls_panel = false
bg_shader_enabled = true
[gui.show_windows] [gui.show_windows]
"Context Hub" = true "Context Hub" = true
@@ -28,12 +34,12 @@ separate_tool_calls_panel = false
"Tier 4: QA" = true "Tier 4: QA" = true
"Discussion Hub" = true "Discussion Hub" = true
"Operations Hub" = true "Operations Hub" = true
Message = true Message = false
Response = true Response = false
"Tool Calls" = true "Tool Calls" = false
Theme = true Theme = true
"Log Management" = true "Log Management" = true
Diagnostics = true Diagnostics = false
[theme] [theme]
palette = "DPG Default" palette = "DPG Default"

View File

@@ -0,0 +1,12 @@
from imgui_bundle import imgui, hello_imgui
def test_font_config():
config = imgui.ImFontConfig()
config.oversample_h = 3
config.oversample_v = 3
print(f"Oversample H: {config.oversample_h}")
print(f"Oversample V: {config.oversample_v}")
if __name__ == "__main__":
test_font_config()

View File

@@ -324,7 +324,8 @@ class AppController:
'manual_approve': 'ui_manual_approve', 'manual_approve': 'ui_manual_approve',
'inject_file_path': '_inject_file_path', 'inject_file_path': '_inject_file_path',
'inject_mode': '_inject_mode', 'inject_mode': '_inject_mode',
'show_inject_modal': '_show_inject_modal' 'show_inject_modal': '_show_inject_modal',
'bg_shader_enabled': 'bg_shader_enabled'
} }
self._gettable_fields = dict(self._settable_fields) self._gettable_fields = dict(self._settable_fields)
self._gettable_fields.update({ self._gettable_fields.update({
@@ -346,7 +347,8 @@ class AppController:
'_inject_file_path': '_inject_file_path', '_inject_file_path': '_inject_file_path',
'_inject_mode': '_inject_mode', '_inject_mode': '_inject_mode',
'_inject_preview': '_inject_preview', '_inject_preview': '_inject_preview',
'_show_inject_modal': '_show_inject_modal' '_show_inject_modal': '_show_inject_modal',
'bg_shader_enabled': 'bg_shader_enabled'
}) })
self.perf_monitor = performance_monitor.get_monitor() self.perf_monitor = performance_monitor.get_monitor()
self._perf_profiling_enabled = False self._perf_profiling_enabled = False
@@ -783,6 +785,11 @@ class AppController:
self.ui_summary_only = proj_meta.get("summary_only", False) self.ui_summary_only = proj_meta.get("summary_only", False)
self.ui_auto_add_history = disc_sec.get("auto_add", False) self.ui_auto_add_history = disc_sec.get("auto_add", False)
self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "") self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "")
gui_cfg = self.config.get("gui", {})
from src import bg_shader
bg_shader.get_bg().enabled = gui_cfg.get("bg_shader_enabled", False)
_default_windows = { _default_windows = {
"Context Hub": True, "Context Hub": True,
"Files & Media": True, "Files & Media": True,
@@ -2077,12 +2084,15 @@ class AppController:
} }
self.config["ai"]["system_prompt"] = self.ui_global_system_prompt self.config["ai"]["system_prompt"] = self.ui_global_system_prompt
self.config["projects"] = {"paths": self.project_paths, "active": self.active_project_path} self.config["projects"] = {"paths": self.project_paths, "active": self.active_project_path}
from src import bg_shader
self.config["gui"] = { self.config["gui"] = {
"show_windows": self.show_windows, "show_windows": self.show_windows,
"separate_message_panel": getattr(self, "ui_separate_message_panel", False), "separate_message_panel": getattr(self, "ui_separate_message_panel", False),
"separate_response_panel": getattr(self, "ui_separate_response_panel", False), "separate_response_panel": getattr(self, "ui_separate_response_panel", False),
"separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False), "separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False),
"bg_shader_enabled": bg_shader.get_bg().enabled
} }
# Explicitly call theme save to ensure self.config is updated
theme.save_to_config(self.config) theme.save_to_config(self.config)
def _do_generate(self) -> tuple[str, Path, list[dict[str, Any]], str, str]: def _do_generate(self) -> tuple[str, Path, list[dict[str, Any]], str, str]:

65
src/bg_shader.py Normal file
View File

@@ -0,0 +1,65 @@
# src/bg_shader.py
import time
import math
from typing import Optional
import numpy as np
from imgui_bundle import imgui, nanovg as nvg, hello_imgui
class BackgroundShader:
def __init__(self):
self.enabled = False
self.start_time = time.time()
self.ctx: Optional[nvg.Context] = None
def render(self, width: float, height: float):
if not self.enabled:
return
# In imgui-bundle, hello_imgui handles the background.
# We can use the background_draw_list to draw primitives.
# Since we don't have raw GLSL easily in Python without PyOpenGL,
# we'll use a "faux-shader" approach with NanoVG or DrawList gradients.
t = time.time() - self.start_time
dl = imgui.get_background_draw_list()
# Base deep sea color
dl.add_rect_filled(imgui.ImVec2(0, 0), imgui.ImVec2(width, height), imgui.get_color_u32(imgui.ImVec4(0.01, 0.07, 0.20, 1.0)))
# Layer 1: Slow moving large blobs (FBM approximation)
for i in range(3):
phase = t * (0.1 + i * 0.05)
x = (math.sin(phase) * 0.5 + 0.5) * width
y = (math.cos(phase * 0.8) * 0.5 + 0.5) * height
radius = (0.4 + 0.2 * math.sin(t * 0.2)) * max(width, height)
col = imgui.ImVec4(0.02, 0.26, 0.55, 0.3)
dl.add_circle_filled(imgui.ImVec2(x, y), radius, imgui.get_color_u32(col), num_segments=32)
# Layer 2: Shimmering caustics (Animated Lines)
num_lines = 15
for i in range(num_lines):
offset = (t * 20.0 + i * (width / num_lines)) % width
alpha = 0.1 * (1.0 + math.sin(t + i))
col = imgui.get_color_u32(imgui.ImVec4(0.08, 0.60, 0.88, alpha))
p1 = imgui.ImVec2(offset, 0)
p2 = imgui.ImVec2(offset - 100, height)
dl.add_line(p1, p2, col, thickness=2.0)
# Vignette
center = imgui.ImVec2(width/2, height/2)
radius = max(width, height) * 0.8
# Draw multiple concentric circles for a soft vignette
for i in range(10):
r = radius + (i * 50)
alpha = (i / 10.0) * 0.5
dl.add_circle(center, r, imgui.get_color_u32(imgui.ImVec4(0, 0, 0, alpha)), num_segments=64, thickness=60.0)
_bg: Optional[BackgroundShader] = None
def get_bg():
global _bg
if _bg is None:
_bg = BackgroundShader()
return _bg

View File

@@ -23,6 +23,7 @@ from src import models
from src import app_controller from src import app_controller
from src import mcp_client from src import mcp_client
from src import markdown_helper from src import markdown_helper
from src import bg_shader
import re import re
from pydantic import BaseModel from pydantic import BaseModel
@@ -287,6 +288,12 @@ class App:
imgui.end_menu() imgui.end_menu()
def _gui_func(self) -> None: def _gui_func(self) -> None:
# Render background shader
bg = bg_shader.get_bg()
if bg.enabled:
ws = imgui.get_io().display_size
bg.render(ws.x, ws.y)
pushed_prior_tint = False pushed_prior_tint = False
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func") if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
if self.is_viewing_prior_session: if self.is_viewing_prior_session:
@@ -2780,6 +2787,8 @@ def hello():
for p in theme.get_palette_names(): for p in theme.get_palette_names():
if imgui.selectable(p, p == cp)[0]: if imgui.selectable(p, p == cp)[0]:
theme.apply(p) theme.apply(p)
self._flush_to_config()
models.save_config(self.config)
imgui.end_combo() imgui.end_combo()
imgui.separator() imgui.separator()
@@ -2815,7 +2824,33 @@ def hello():
imgui.separator() imgui.separator()
imgui.text("UI Scale (DPI)") imgui.text("UI Scale (DPI)")
ch, scale = imgui.slider_float("##scale", theme.get_current_scale(), 0.5, 3.0, "%.2f") ch, scale = imgui.slider_float("##scale", theme.get_current_scale(), 0.5, 3.0, "%.2f")
if ch: theme.set_scale(scale) if ch:
theme.set_scale(scale)
self._flush_to_config()
models.save_config(self.config)
imgui.text("Panel Transparency")
ch_t, trans = imgui.slider_float("##trans", theme.get_transparency(), 0.1, 1.0, "%.2f")
if ch_t:
theme.set_transparency(trans)
self._flush_to_config()
models.save_config(self.config)
imgui.text("Panel Item Transparency")
ch_ct, ctrans = imgui.slider_float("##ctrans", theme.get_child_transparency(), 0.1, 1.0, "%.2f")
if ch_ct:
theme.set_child_transparency(ctrans)
self._flush_to_config()
models.save_config(self.config)
imgui.separator()
bg = bg_shader.get_bg()
ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled)
if ch_bg:
gui_cfg = self.config.setdefault("gui", {})
gui_cfg["bg_shader_enabled"] = bg.enabled
self._flush_to_config()
models.save_config(self.config)
imgui.end() imgui.end()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_theme_panel") if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_theme_panel")
@@ -2825,12 +2860,17 @@ def hello():
if assets_dir.exists(): if assets_dir.exists():
hello_imgui.set_assets_folder(str(assets_dir.absolute())) hello_imgui.set_assets_folder(str(assets_dir.absolute()))
# Improved font rendering with oversampling
config = imgui.ImFontConfig()
config.oversample_h = 3
config.oversample_v = 3
font_path, font_size = theme.get_font_loading_params() font_path, font_size = theme.get_font_loading_params()
if font_path: if font_path:
# Just try loading it directly; hello_imgui will look in the assets folder # Just try loading it directly; hello_imgui will look in the assets folder
try: try:
self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size) self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config)
except Exception as e: except Exception as e:
print(f"Failed to load main font {font_path}: {e}") print(f"Failed to load main font {font_path}: {e}")
self.main_font = None self.main_font = None
@@ -2838,7 +2878,8 @@ def hello():
self.main_font = None self.main_font = None
try: try:
self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size) params = hello_imgui.FontLoadingParams(font_config=config)
self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params)
except Exception as e: except Exception as e:
print(f"Failed to load mono font: {e}") print(f"Failed to load mono font: {e}")
self.mono_font = None self.mono_font = None
@@ -2862,8 +2903,14 @@ def hello():
self.runner_params.app_window_params.window_title = "manual slop" self.runner_params.app_window_params.window_title = "manual slop"
self.runner_params.app_window_params.window_geometry.size = (1680, 1200) self.runner_params.app_window_params.window_geometry.size = (1680, 1200)
self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False) self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
self.runner_params.imgui_window_params.remember_theme = True
self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme()
self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
# Enforce DPI Awareness and User Scale
user_scale = theme.get_current_scale()
self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale
# Detect Monitor Refresh Rate for capping # Detect Monitor Refresh Rate for capping
fps_cap = 60.0 fps_cap = 60.0
try: try:
@@ -2879,11 +2926,13 @@ def hello():
self.runner_params.fps_idling.fps_idle = fps_cap self.runner_params.fps_idling.fps_idle = fps_cap
self.runner_params.imgui_window_params.show_menu_bar = True self.runner_params.imgui_window_params.show_menu_bar = True
self.runner_params.imgui_window_params.show_menu_view_themes = True
self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder
self.runner_params.ini_filename = "manualslop_layout.ini" self.runner_params.ini_filename = "manualslop_layout.ini"
self.runner_params.callbacks.show_gui = self._gui_func self.runner_params.callbacks.show_gui = self._gui_func
self.runner_params.callbacks.show_menus = self._show_menus self.runner_params.callbacks.show_menus = self._show_menus
self.runner_params.callbacks.load_additional_fonts = self._load_fonts self.runner_params.callbacks.load_additional_fonts = self._load_fonts
self.runner_params.callbacks.setup_imgui_style = theme.apply_current
self.runner_params.callbacks.post_init = self._post_init self.runner_params.callbacks.post_init = self._post_init
self._fetch_models(self.current_provider) self._fetch_models(self.current_provider)
md_options = markdown_helper.get_renderer().options md_options = markdown_helper.get_renderer().options

View File

@@ -3,12 +3,13 @@
Theming support for manual_slop GUI — imgui-bundle port. Theming support for manual_slop GUI — imgui-bundle port.
Replaces theme.py (DearPyGui-specific) with imgui-bundle equivalents. Replaces theme.py (DearPyGui-specific) with imgui-bundle equivalents.
Palettes are applied via imgui.get_style().set_color_() calls. Palettes are applied via imgui.get_style().set_color_() calls or hello_imgui.apply_theme().
Font loading uses hello_imgui.load_font(). Font loading uses hello_imgui.load_font().
Scale uses imgui.get_style().font_scale_main. Scale uses imgui.get_style().font_scale_main.
""" """
from imgui_bundle import imgui from imgui_bundle import imgui, hello_imgui
from typing import Any, Optional
# ------------------------------------------------------------------ palettes # ------------------------------------------------------------------ palettes
@@ -173,23 +174,68 @@ _PALETTES: dict[str, dict[int, tuple]] = {
imgui.Col_.nav_cursor: _c(166, 226, 46), imgui.Col_.nav_cursor: _c(166, 226, 46),
imgui.Col_.modal_window_dim_bg: _c( 10, 10, 8, 100), imgui.Col_.modal_window_dim_bg: _c( 10, 10, 8, 100),
}, },
"Binks": {
imgui.Col_.text: _c( 0, 0, 0, 255),
imgui.Col_.text_disabled: _c(153, 153, 153, 255),
imgui.Col_.window_bg: _c(240, 240, 240, 240),
imgui.Col_.child_bg: _c( 0, 0, 0, 0),
imgui.Col_.popup_bg: _c(255, 255, 255, 240),
imgui.Col_.border: _c( 0, 0, 0, 99),
imgui.Col_.border_shadow: _c(255, 255, 255, 25),
imgui.Col_.frame_bg: _c(255, 255, 255, 240),
imgui.Col_.frame_bg_hovered: _c( 66, 150, 250, 102),
imgui.Col_.frame_bg_active: _c( 66, 150, 250, 171),
imgui.Col_.title_bg: _c(245, 245, 245, 255),
imgui.Col_.title_bg_collapsed: _c(255, 255, 255, 130),
imgui.Col_.title_bg_active: _c(209, 209, 209, 255),
imgui.Col_.menu_bar_bg: _c(219, 219, 219, 255),
imgui.Col_.scrollbar_bg: _c(250, 250, 250, 135),
imgui.Col_.scrollbar_grab: _c(176, 176, 176, 255),
imgui.Col_.scrollbar_grab_hovered: _c(150, 150, 150, 255),
imgui.Col_.scrollbar_grab_active: _c(125, 125, 125, 255),
imgui.Col_.check_mark: _c( 66, 150, 250, 255),
imgui.Col_.slider_grab: _c( 61, 133, 224, 255),
imgui.Col_.slider_grab_active: _c( 66, 150, 250, 255),
imgui.Col_.button: _c( 66, 150, 250, 102),
imgui.Col_.button_hovered: _c( 66, 150, 250, 255),
imgui.Col_.button_active: _c( 15, 135, 250, 255),
imgui.Col_.header: _c( 66, 150, 250, 79),
imgui.Col_.header_hovered: _c( 66, 150, 250, 204),
imgui.Col_.header_active: _c( 66, 150, 250, 255),
imgui.Col_.separator: _c(100, 100, 100, 255),
imgui.Col_.resize_grip: _c(255, 255, 255, 127),
imgui.Col_.resize_grip_hovered: _c( 66, 150, 250, 171),
imgui.Col_.resize_grip_active: _c( 66, 150, 250, 242),
imgui.Col_.plot_lines: _c( 99, 99, 99, 255),
imgui.Col_.plot_lines_hovered: _c(255, 110, 89, 255),
imgui.Col_.plot_histogram: _c(230, 178, 0, 255),
imgui.Col_.plot_histogram_hovered: _c(255, 153, 0, 255),
imgui.Col_.text_selected_bg: _c( 66, 150, 250, 89),
imgui.Col_.modal_window_dim_bg: _c( 51, 51, 51, 89),
},
} }
PALETTE_NAMES: list[str] = list(_PALETTES.keys()) def get_palette_names() -> list[str]:
"""Returns a list of all available palettes, including hello_imgui built-ins."""
names = list(_PALETTES.keys())
# Add hello_imgui themes
hi_themes = [name for name in dir(hello_imgui.ImGuiTheme_) if not name.startswith('_') and name != 'count']
# Filter out int methods that leaked into dir() if any
hi_themes = [n for n in hi_themes if not hasattr(int, n)]
names.extend(sorted(hi_themes))
return names
# ------------------------------------------------------------------ state # ------------------------------------------------------------------ state
_current_palette: str = "ImGui Dark" _current_palette: str = "10x Dark"
_current_font_path: str = "" _current_font_path: str = "fonts/Inter-Regular.ttf"
_current_font_size: float = 16.0 _current_font_size: float = 16.0
_current_scale: float = 1.0 _current_scale: float = 1.0
_custom_font: imgui.ImFont = None # type: ignore _transparency: float = 1.0
_child_transparency: float = 1.0
# ------------------------------------------------------------------ public API # ------------------------------------------------------------------ public API
def get_palette_names() -> list[str]:
return list(_PALETTES.keys())
def get_current_palette() -> str: def get_current_palette() -> str:
return _current_palette return _current_palette
@@ -202,18 +248,49 @@ def get_current_font_size() -> float:
def get_current_scale() -> float: def get_current_scale() -> float:
return _current_scale return _current_scale
def get_transparency() -> float:
return _transparency
def set_transparency(val: float) -> None:
global _transparency
_transparency = val
apply(_current_palette)
def get_child_transparency() -> float:
return _child_transparency
def set_child_transparency(val: float) -> None:
global _child_transparency
_child_transparency = val
apply(_current_palette)
def apply(palette_name: str) -> None: def apply(palette_name: str) -> None:
""" """
Apply a named palette by setting all ImGui style colors and applying global professional styling. Apply a named palette by setting all ImGui style colors and applying global professional styling.
Call this once per frame if you want dynamic switching, or once at startup.
In practice we call it once when the user picks a palette, and imgui retains the style.
""" """
global _current_palette global _current_palette
_current_palette = palette_name _current_palette = palette_name
colours = _PALETTES.get(palette_name, {})
style = imgui.get_style()
# Subtle Rounding Professional Theme # 1. Apply base colors
if palette_name in _PALETTES:
colours = _PALETTES[palette_name]
imgui.style_colors_dark()
style = imgui.get_style()
for col_enum, rgba in colours.items():
style.set_color_(col_enum, imgui.ImVec4(*rgba))
elif hasattr(hello_imgui.ImGuiTheme_, palette_name):
theme_enum = getattr(hello_imgui.ImGuiTheme_, palette_name)
hello_imgui.apply_theme(theme_enum)
else:
# Fallback to Nord Dark if requested but not found, otherwise ImGui Dark
if palette_name == "Nord Dark":
# This should not happen since it's in _PALETTES, but for safety
imgui.style_colors_dark()
else:
imgui.style_colors_dark()
# 2. Apply our "Subtle Rounding" professional tweaks on top of ANY theme
style = imgui.get_style()
style.window_rounding = 6.0 style.window_rounding = 6.0
style.child_rounding = 4.0 style.child_rounding = 4.0
style.frame_rounding = 4.0 style.frame_rounding = 4.0
@@ -225,6 +302,17 @@ def apply(palette_name: str) -> None:
style.frame_border_size = 1.0 style.frame_border_size = 1.0
style.popup_border_size = 1.0 style.popup_border_size = 1.0
# Apply transparency to WindowBg
win_bg = style.color_(imgui.Col_.window_bg)
win_bg.w = _transparency
style.set_color_(imgui.Col_.window_bg, win_bg)
# Apply child/frame transparency
for col_idx in [imgui.Col_.child_bg, imgui.Col_.frame_bg, imgui.Col_.popup_bg]:
c = style.color_(col_idx)
c.w = _child_transparency
style.set_color_(col_idx, c)
# Spacing & Padding # Spacing & Padding
style.window_padding = imgui.ImVec2(8.0, 8.0) style.window_padding = imgui.ImVec2(8.0, 8.0)
style.frame_padding = imgui.ImVec2(8.0, 4.0) style.frame_padding = imgui.ImVec2(8.0, 4.0)
@@ -237,16 +325,6 @@ def apply(palette_name: str) -> None:
style.anti_aliased_fill = True style.anti_aliased_fill = True
style.anti_aliased_lines_use_tex = True style.anti_aliased_lines_use_tex = True
if not colours:
# Reset to imgui dark defaults
imgui.style_colors_dark()
return
# Start from dark defaults so unlisted keys have sensible values
imgui.style_colors_dark()
for col_enum, rgba in colours.items():
style.set_color_(col_enum, imgui.ImVec4(*rgba))
def set_scale(factor: float) -> None: def set_scale(factor: float) -> None:
"""Set the global font/UI scale factor.""" """Set the global font/UI scale factor."""
global _current_scale global _current_scale
@@ -261,17 +339,22 @@ def save_to_config(config: dict) -> None:
config["theme"]["font_path"] = _current_font_path config["theme"]["font_path"] = _current_font_path
config["theme"]["font_size"] = _current_font_size config["theme"]["font_size"] = _current_font_size
config["theme"]["scale"] = _current_scale config["theme"]["scale"] = _current_scale
config["theme"]["transparency"] = _transparency
config["theme"]["child_transparency"] = _child_transparency
def load_from_config(config: dict) -> None: def load_from_config(config: dict) -> None:
"""Read [theme] from config and apply palette + scale. Font is handled separately at startup.""" """Read [theme] from config. Font is handled separately at startup."""
global _current_font_path, _current_font_size, _current_scale, _current_palette global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency
t = config.get("theme", {}) t = config.get("theme", {})
_current_palette = t.get("palette", "ImGui Dark") _current_palette = t.get("palette", "10x Dark")
if _current_palette in ("", "DPG Default"):
_current_palette = "10x Dark"
_current_font_path = t.get("font_path", "fonts/Inter-Regular.ttf") _current_font_path = t.get("font_path", "fonts/Inter-Regular.ttf")
_current_font_size = float(t.get("font_size", 16.0)) _current_font_size = float(t.get("font_size", 16.0))
_current_scale = float(t.get("scale", 1.0)) _current_scale = float(t.get("scale", 1.0))
# Don't apply here — imgui context may not exist yet. _transparency = float(t.get("transparency", 1.0))
# Call apply_current() after imgui is initialised. _child_transparency = float(t.get("child_transparency", 1.0))
def apply_current() -> None: def apply_current() -> None:
"""Apply the loaded palette and scale. Call after imgui context exists.""" """Apply the loaded palette and scale. Call after imgui context exists."""
@@ -281,3 +364,15 @@ def apply_current() -> None:
def get_font_loading_params() -> tuple[str, float]: def get_font_loading_params() -> tuple[str, float]:
"""Return (font_path, font_size) for use during hello_imgui font loading callback.""" """Return (font_path, font_size) for use during hello_imgui font loading callback."""
return _current_font_path, _current_font_size return _current_font_path, _current_font_size
def get_tweaked_theme() -> hello_imgui.ImGuiTweakedTheme:
"""Returns an ImGuiTweakedTheme object reflecting the current state."""
tt = hello_imgui.ImGuiTweakedTheme()
if hasattr(hello_imgui.ImGuiTheme_, _current_palette):
tt.theme = getattr(hello_imgui.ImGuiTheme_, _current_palette)
else:
tt.theme = hello_imgui.ImGuiTheme_.imgui_colors_dark
# Sync tweaks
tt.tweaks.rounding = 6.0
return tt