Implement speech style toolbar live-edit refinements and whole speech show controls
- Added a new specification for the speech style toolbar live-edit, detailing immediate style application, scoped effect demonstration, and hover feedback. - Introduced refinements to the speech style toolbar, including improved chooser sizing, exclusive stage editing during speech composition, and removal of redundant controls. - Documented new stage controls for managing how the whole speech shows, including options for bubble arrangement, word appearance, and bubble motion. - Created a new log file for speech padding syntax to track engine versioning.
This commit is contained in:
@@ -8,14 +8,15 @@ const DEFAULT_PATH: String = "res://sandbox_theme.json"
|
||||
const DEFAULT_CONFIG: Dictionary = {
|
||||
"version": SCHEMA_VERSION,
|
||||
"global": {"font_preset": "ui", "font_size": 18, "text_color": "#F5F7FA", "muted_text_color": "#AAB2C0", "bg_color": "#171B24F2", "surface_color": "#252B38FA", "border_color": "#FFFFFF33", "border_width": 1, "corner_radius": 10, "padding": 8, "spacing": 8},
|
||||
"fonts": {"ui_font": "", "emoji_font": "", "ui_font_bold": "", "ui_font_italic": "", "presets": {"ui": {"label": "Standard", "path": ""}}, "sizes": {"action_popup": 24, "action_popup_emoji": 22, "assignment_badge": 20, "rule_label": 18, "status_pill": 18, "tooltip": 18, "file_menu": 22, "file_menu_popup": 26, "mode_switcher": 24, "timestamp_badge": 22, "queue_panel": 22, "rule_panel": 22, "action_editor": 22, "rule_editor": 22, "panel_row": 20, "panel_title": 24, "asset_label": 18, "asset_badge": 14}, "flags": {"panel_title_bold": true, "rule_label_bold": false, "badge_bold": true, "action_popup_bold": false, "action_popup_italic": false}},
|
||||
"fonts": {"ui_font": "", "emoji_font": "", "ui_font_bold": "", "ui_font_italic": "", "presets": {"ui": {"label": "Standard", "path": ""}, "comic": {"label": "Comic", "path": "res://resources/fonts/comic.ttf", "speech_base_size": 28}, "pixel": {"label": "Pixel", "path": "res://resources/fonts/pixel.ttf", "speech_base_size": 40}, "scary": {"label": "Scary", "path": "res://resources/fonts/scary.ttf", "speech_base_size": 28}}, "sizes": {"action_popup": 24, "action_popup_emoji": 22, "assignment_badge": 20, "rule_label": 18, "status_pill": 18, "tooltip": 18, "file_menu": 22, "file_menu_popup": 26, "mode_switcher": 24, "timestamp_badge": 22, "queue_panel": 22, "rule_panel": 22, "action_editor": 22, "rule_editor": 22, "panel_row": 20, "panel_title": 24, "asset_label": 18, "asset_badge": 14}, "flags": {"panel_title_bold": true, "rule_label_bold": false, "badge_bold": true, "action_popup_bold": false, "action_popup_italic": false}},
|
||||
"mode_colors": {"edit": "#22C6FF", "direct": "#FFB300", "play": "#33DD77", "guide": "#22C6FF"},
|
||||
"grid": {"snap_size": 15.0},
|
||||
"chrome": {"style_defaults": {}, "top_bar": {"height": 64}, "status_bar": {"height": 38}, "mode_switcher": {"segment_min_width": 124}, "mode_frame": {"border_width": 3}, "mode_badge": {"corner_radius": 12, "padding": 10}, "timestamp_badge": {"corner_radius": 10, "padding": 8, "bg_color": "#171B24F2"}, "tooltip": {"bg_color": "#101010E6", "border_width": 1}, "dialogs": {"queue_size":[680,520], "rule_size":[720,560], "action_size":[560,390], "confirm_size":[620,300], "font_size":22, "message_font_size":26}},
|
||||
"popups": {"style_defaults": {"bg_color": "#202633FA", "border_color": "#FFFFFF33", "corner_radius": 12, "padding": 12}, "action_menu": {"font_size": 24}, "dialog": {}, "asset_selector": {"backdrop_alpha": 0.55, "cell_size":[190,220], "thumbnail_size":[156,156]}},
|
||||
"palette": {"style_defaults": {}, "dock_width": 116, "card_size": [96, 94], "icon_size": 30, "drag_threshold": 8, "selected_border_width": 3},
|
||||
"overlays": {"style_defaults": {"line_width": 2, "dash_length": 8, "dash_gap": 5}, "terrain_guide": {"color": "#22C6FF"}, "completed_path_alpha": 0.20, "completed_path_color": "#959EAD", "valid": {"color": "#4DE66680"}, "same_type": {"color": "#FFFFFF26"}, "invalid": {"color": "#FF4D4D80"}},
|
||||
"widgets": {"swatches": ["#FFFFFF", "#111111", "#22C6FF", "#FFB300", "#33DD77", "#FF4D4D"], "size_presets": [{"id":"medium", "label":"Medium", "value":28}], "emoji": ["😀", "😂", "😍", "😮", "😢", "😡", "👍", "❤️"]}
|
||||
"widgets": {"swatches": ["#FFFFFF", "#111111", "#22C6FF", "#FFB300", "#33DD77", "#FF4D4D"], "size_presets": [{"id":"small", "label":"Small", "value":20}, {"id":"medium", "label":"Medium", "value":28}, {"id":"huge", "label":"HUGE", "value":40}], "emoji": ["😀", "😂", "😍", "😮", "😢", "😡", "👍", "❤️"]},
|
||||
"speech": {"toolbar": {"surface":"#1C2431F7", "border":"#9EADBF", "selection_halo":"#FFD84D", "gap":10, "max_width":480, "min_hit_height":48}, "ui": {"panel_width": 440, "button_min_height": 44, "heading_size": 24}, "layout": {"max_width": 420, "stack_gap": 12, "screen_margin": 18, "bubble_padding": 18}, "animation": {"letter_interval": 0.045, "word_interval": 0.20, "pop_duration": 0.24}, "styles": {"talk": {"fill":"#FFFDF6", "outline":"#202020", "tail":"talk"}, "shout": {"fill":"#FFD642", "outline":"#D92F26", "tail":"shout"}, "think": {"fill":"#E9F5FF", "outline":"#37698B", "tail":"think"}, "whisper": {"fill":"#F2F2F2", "outline":"#6D7885", "tail":"whisper"}, "sfx": {"fill":"#FFDD39", "outline":"#D92F26", "tail":"none"}}, "text_colors": [{"name":"Ink","color":"#202020"},{"name":"Midnight","color":"#111827"},{"name":"Slate","color":"#475569"},{"name":"Snow","color":"#FFFFFF"},{"name":"Cloud","color":"#CBD5E1"},{"name":"Sky","color":"#2196F3"},{"name":"Ocean","color":"#0077B6"},{"name":"Teal","color":"#00897B"},{"name":"Leaf","color":"#2E7D32"},{"name":"Lime","color":"#8BC34A"},{"name":"Sunshine","color":"#FFC107"},{"name":"Orange","color":"#F57C00"},{"name":"Sunset red","color":"#E53935"},{"name":"Rose","color":"#D81B60"},{"name":"Violet","color":"#7E57C2"},{"name":"Brown","color":"#795548"}], "preview_ghost":"#FFFFFF99", "connector":"#FFFFFFB0"}
|
||||
}
|
||||
|
||||
var _config: Dictionary = DEFAULT_CONFIG.duplicate(true)
|
||||
@@ -81,7 +82,18 @@ func font(component_path: String = "", preset_id: String = "ui") -> Font:
|
||||
if not component_path.is_empty(): path = String(value(component_path + ".font_path", ""))
|
||||
if path.is_empty(): path = String(value("fonts.presets.%s.path" % preset_id, ""))
|
||||
if path.is_empty(): path = String(value("fonts.ui_font", ""))
|
||||
if path.is_empty(): return ThemeDB.fallback_font
|
||||
if path.is_empty():
|
||||
if preset_id == "ui": return ThemeDB.fallback_font
|
||||
var system_key: String = "system:" + preset_id
|
||||
if _font_cache.has(system_key): return _font_cache[system_key]
|
||||
var system_font := SystemFont.new()
|
||||
match preset_id:
|
||||
"comic": system_font.font_names = PackedStringArray(["Comic Sans MS", "Comic Neue", "Chalkboard", "Comic Sans", "sans-serif"])
|
||||
"pixel": system_font.font_names = PackedStringArray(["Consolas", "Courier New", "Liberation Mono", "monospace"])
|
||||
"scary": system_font.font_names = PackedStringArray(["Impact", "Chiller", "Papyrus", "fantasy", "sans-serif"])
|
||||
_: system_font.font_names = PackedStringArray(["Arial", "Liberation Sans", "sans-serif"])
|
||||
_font_cache[system_key] = system_font
|
||||
return system_font
|
||||
if _font_cache.has(path): return _font_cache[path]
|
||||
var resource: Resource = ResourceLoader.load(path) if ResourceLoader.exists(path) else null
|
||||
if resource is Font:
|
||||
@@ -90,6 +102,18 @@ func font(component_path: String = "", preset_id: String = "ui") -> Font:
|
||||
_warn_once("font." + path, "SandboxTheme: font '%s' is unavailable; using fallback." % path)
|
||||
return ThemeDB.fallback_font
|
||||
|
||||
func speech_font(preset_id: String = "ui") -> Font:
|
||||
return font("", preset_id)
|
||||
|
||||
|
||||
func speech_font_size(preset_id: String, size_preset: String) -> int:
|
||||
var base_size: int = integer("fonts.presets.%s.speech_base_size" % preset_id, 28, 8, 96)
|
||||
var scale: float = 1.0
|
||||
match size_preset:
|
||||
"small": scale = 0.72
|
||||
"huge": scale = 1.43
|
||||
return clampi(roundi(float(base_size) * scale), 8, 128)
|
||||
|
||||
func emoji_font() -> Font:
|
||||
var path: String = String(value("fonts.emoji_font", ""))
|
||||
if path.is_empty(): return font()
|
||||
@@ -152,6 +176,45 @@ func emoji_items() -> Array[String]:
|
||||
for item: Variant in value("widgets.emoji", []): if item is String: result.append(String(item))
|
||||
return result
|
||||
|
||||
func speech_color(frame: String, channel: String, fallback: Color) -> Color:
|
||||
if not ["talk", "shout", "think", "whisper", "sfx"].has(frame) or not ["fill", "outline"].has(channel):
|
||||
return fallback
|
||||
return color("speech.styles.%s.%s" % [frame, channel], fallback)
|
||||
|
||||
func speech_number(token: String, fallback: float, minimum: float, maximum: float) -> float:
|
||||
if not ["ui.panel_width", "ui.button_min_height", "ui.heading_size", "layout.max_width", "layout.stack_gap", "layout.screen_margin", "layout.bubble_padding", "animation.letter_interval", "animation.word_interval", "animation.pop_duration"].has(token):
|
||||
return fallback
|
||||
return number("speech." + token, fallback, minimum, maximum)
|
||||
|
||||
|
||||
func speech_toolbar_number(token: String, fallback: float, minimum: float, maximum: float) -> float:
|
||||
if not ["gap", "max_width", "min_hit_height"].has(token):
|
||||
return fallback
|
||||
return number("speech.toolbar." + token, fallback, minimum, maximum)
|
||||
|
||||
func speech_text_colors() -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
var raw_colors: Variant = value("speech.text_colors", [])
|
||||
if not raw_colors is Array:
|
||||
return result
|
||||
for raw: Variant in raw_colors as Array:
|
||||
if not raw is Dictionary:
|
||||
continue
|
||||
var item: Dictionary = raw as Dictionary
|
||||
var name: String = String(item.get("name", "")).strip_edges()
|
||||
var color_text: String = String(item.get("color", ""))
|
||||
if not name.is_empty() and _is_valid_hex_color(color_text):
|
||||
result.append({"name": name, "color": Color.from_string(color_text, Color.BLACK)})
|
||||
return result
|
||||
|
||||
func _is_valid_hex_color(text: String) -> bool:
|
||||
if text.length() != 7 and text.length() != 9 or not text.begins_with("#"):
|
||||
return false
|
||||
for index: int in range(1, text.length()):
|
||||
if not "0123456789abcdefABCDEF".contains(text.substr(index, 1)):
|
||||
return false
|
||||
return true
|
||||
|
||||
func invalidate_caches() -> void:
|
||||
_font_cache.clear(); _style_cache.clear(); _control_theme = null; _warned.clear()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user