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
+124 -29
View File
@@ -3,12 +3,13 @@
Theming support for manual_slop GUI — imgui-bundle port.
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().
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
@@ -173,23 +174,68 @@ _PALETTES: dict[str, dict[int, tuple]] = {
imgui.Col_.nav_cursor: _c(166, 226, 46),
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
_current_palette: str = "ImGui Dark"
_current_font_path: str = ""
_current_palette: str = "10x Dark"
_current_font_path: str = "fonts/Inter-Regular.ttf"
_current_font_size: float = 16.0
_current_scale: float = 1.0
_custom_font: imgui.ImFont = None # type: ignore
_transparency: float = 1.0
_child_transparency: float = 1.0
# ------------------------------------------------------------------ public API
def get_palette_names() -> list[str]:
return list(_PALETTES.keys())
def get_current_palette() -> str:
return _current_palette
@@ -202,18 +248,49 @@ def get_current_font_size() -> float:
def get_current_scale() -> float:
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:
"""
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
_current_palette = palette_name
colours = _PALETTES.get(palette_name, {})
style = imgui.get_style()
# 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()
# Subtle Rounding Professional Theme
# 2. Apply our "Subtle Rounding" professional tweaks on top of ANY theme
style = imgui.get_style()
style.window_rounding = 6.0
style.child_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.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
style.window_padding = imgui.ImVec2(8.0, 8.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_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:
"""Set the global font/UI scale factor."""
global _current_scale
@@ -261,17 +339,22 @@ def save_to_config(config: dict) -> None:
config["theme"]["font_path"] = _current_font_path
config["theme"]["font_size"] = _current_font_size
config["theme"]["scale"] = _current_scale
config["theme"]["transparency"] = _transparency
config["theme"]["child_transparency"] = _child_transparency
def load_from_config(config: dict) -> None:
"""Read [theme] from config and apply palette + scale. Font is handled separately at startup."""
global _current_font_path, _current_font_size, _current_scale, _current_palette
"""Read [theme] from config. Font is handled separately at startup."""
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency
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_size = float(t.get("font_size", 16.0))
_current_scale = float(t.get("scale", 1.0))
# Don't apply here — imgui context may not exist yet.
# Call apply_current() after imgui is initialised.
_transparency = float(t.get("transparency", 1.0))
_child_transparency = float(t.get("child_transparency", 1.0))
def apply_current() -> None:
"""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]:
"""Return (font_path, font_size) for use during hello_imgui font loading callback."""
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