diff --git a/.gitignore b/.gitignore index 66b8dcb..a3a763d 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,8 @@ dkms.conf .graptos-save-*.tmp .graptos-latex-build/ build/ +.zig-cache/ +git_check.sh # Github actions .github/ diff --git a/Makefile b/Makefile index c8773e0..dd338c0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ APP_NAME := graptos APP_ID := io.github.graptos.Editor -VERSION := 0.23.61 +VERSION := 0.23.66 PREFIX ?= /usr/local CC ?= cc BUILD_DIR := build @@ -151,6 +151,9 @@ install: $(BUILD_DIR)/$(APP_NAME) install -m644 data/logos/*.png $(DESTDIR)$(DATADIR)/logos/ install -d $(DESTDIR)$(DATADIR)/themes install -m644 data/themes/*.ini $(DESTDIR)$(DATADIR)/themes/ + @if ls data/themes/*.css >/dev/null 2>&1; then \ + install -m644 data/themes/*.css $(DESTDIR)$(DATADIR)/themes/; \ + fi install -d $(DESTDIR)$(DATADIR)/fonts/Inconsolata cp -R data/fonts/Inconsolata/. $(DESTDIR)$(DATADIR)/fonts/Inconsolata/ install -d $(DESTDIR)$(DATADIR)/project-templates diff --git a/data/project-templates/zig-cli/files/.zig-cache/h/f02a51312eeaf2ee19135d11c59d453d.txt b/data/project-templates/zig-cli/files/.zig-cache/h/f02a51312eeaf2ee19135d11c59d453d.txt new file mode 100644 index 0000000..e69de29 diff --git a/data/project-templates/zig-cli/files/.zig-cache/h/timestamp b/data/project-templates/zig-cli/files/.zig-cache/h/timestamp new file mode 100644 index 0000000..e69de29 diff --git a/data/project-templates/zig-cli/files/.zig-cache/o/a9cfc9d8fc3d796eec03d5c3167c0979/dependencies.zig b/data/project-templates/zig-cli/files/.zig-cache/o/a9cfc9d8fc3d796eec03d5c3167c0979/dependencies.zig new file mode 100644 index 0000000..72e4e83 --- /dev/null +++ b/data/project-templates/zig-cli/files/.zig-cache/o/a9cfc9d8fc3d796eec03d5c3167c0979/dependencies.zig @@ -0,0 +1,2 @@ +pub const packages = struct {}; +pub const root_deps: []const struct { []const u8, []const u8 } = &.{}; diff --git a/data/project-templates/zig-cli/files/.zig-cache/z/2e613ea3c2ebff63574623654e263dca b/data/project-templates/zig-cli/files/.zig-cache/z/2e613ea3c2ebff63574623654e263dca new file mode 100644 index 0000000..f038b58 Binary files /dev/null and b/data/project-templates/zig-cli/files/.zig-cache/z/2e613ea3c2ebff63574623654e263dca differ diff --git a/data/project-templates/zig-cli/files/.zig-cache/z/4835cbcb3a4498781e52c9f586f740dd b/data/project-templates/zig-cli/files/.zig-cache/z/4835cbcb3a4498781e52c9f586f740dd new file mode 100644 index 0000000..c0d3998 Binary files /dev/null and b/data/project-templates/zig-cli/files/.zig-cache/z/4835cbcb3a4498781e52c9f586f740dd differ diff --git a/data/project-templates/zig-cli/files/.zig-cache/z/994698d947d63e593a5b4f572b3cad9b b/data/project-templates/zig-cli/files/.zig-cache/z/994698d947d63e593a5b4f572b3cad9b new file mode 100644 index 0000000..a587abb Binary files /dev/null and b/data/project-templates/zig-cli/files/.zig-cache/z/994698d947d63e593a5b4f572b3cad9b differ diff --git a/data/project-templates/zig-cli/files/.zig-cache/z/c4ff8b1c5c08f9226d340aa8f8943e1e b/data/project-templates/zig-cli/files/.zig-cache/z/c4ff8b1c5c08f9226d340aa8f8943e1e new file mode 100644 index 0000000..669e751 Binary files /dev/null and b/data/project-templates/zig-cli/files/.zig-cache/z/c4ff8b1c5c08f9226d340aa8f8943e1e differ diff --git a/data/themes/apprentice.css b/data/themes/apprentice.css new file mode 100644 index 0000000..2304227 --- /dev/null +++ b/data/themes/apprentice.css @@ -0,0 +1,107 @@ +/* Graptoς CSS theme generated from apprentice.ini. */ +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #262626; +@define-color graptos_editor_fg #bcbcbc; +@define-color graptos_editor_gutter_bg #1c1c1c; +@define-color graptos_editor_gutter_fg #6c6c6c; +@define-color graptos_editor_current_line_bg #303030; +@define-color graptos_editor_selection_bg #444444; +@define-color graptos_editor_selection_fg #ffffff; +@define-color graptos_editor_cursor #bcbcbc; +@define-color graptos_sidebar_bg #1c1c1c; +@define-color graptos_tabbar_bg #1c1c1c; +@define-color graptos_tabbar_fg #bcbcbc; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #262626; +@define-color graptos_tab_active_fg #ffffff; +@define-color graptos_tab_active_border #5f5f87; +@define-color graptos_tab_tiled_indicator #5f5f87; +@define-color graptos_topbar_bg #1c1c1c; +@define-color graptos_topbar_fg #bcbcbc; +@define-color graptos_bottombar_bg #1c1c1c; +@define-color graptos_bottombar_fg #bcbcbc; +@define-color graptos_status_error #af5f5f; +@define-color graptos_button_bg #303030; +@define-color graptos_button_fg #bcbcbc; +@define-color graptos_button_hover_bg #444444; +@define-color graptos_button_active_bg #5f5f87; +@define-color graptos_input_bg #1c1c1c; +@define-color graptos_input_fg #bcbcbc; +@define-color graptos_input_border #444444; +@define-color graptos_project_tree_fg #bcbcbc; +@define-color graptos_project_tree_selected_bg #444444; +@define-color graptos_project_tree_selected_fg #ffffff; +@define-color graptos_git_status_modified #87875f; +@define-color graptos_git_status_added #5f875f; +@define-color graptos_git_status_deleted #af5f5f; +@define-color graptos_git_status_renamed #5f87af; +@define-color graptos_git_status_conflict #5f5f87; +@define-color graptos_git_status_untracked #5f8787; +@define-color graptos_git_status_staged #5f5f87; +@define-color graptos_scroll_preview_bg #1c1c1c; +@define-color graptos_scroll_preview_fg #6c6c6c; +@define-color graptos_popover_bg #262626; +@define-color graptos_popover_border #00000000; +@define-color graptos_tooltip_bg #262626; +@define-color graptos_tooltip_fg #bcbcbc; +@define-color graptos_tooltip_border #00000000; +@define-color graptos_ref_popover_bg #262626; +@define-color graptos_ref_popover_fg #bcbcbc; +@define-color graptos_ref_popover_heading #5f5f87; +@define-color graptos_ref_popover_title #5f87af; +@define-color graptos_ref_popover_kind #87875f; +@define-color graptos_ref_popover_snippet #bcbcbc; +@define-color graptos_ref_popover_hover_bg #444444; +@define-color graptos_ref_popover_hover_fg #ffffff; +@define-color graptos_completion_popover_bg #262626; +@define-color graptos_completion_popover_fg #bcbcbc; +@define-color graptos_completion_popover_detail #87875f; +@define-color graptos_completion_selection_bg #5f5f87; +@define-color graptos_completion_selection_fg #ffffff; +@define-color graptos_dialog_bg #262626; +@define-color graptos_dialog_fg #bcbcbc; +@define-color graptos_dialog_border #00000000; +@define-color graptos_dialog_title #ffffff; +@define-color graptos_dialog_body #bcbcbc; +@define-color graptos_dialog_muted #87875f; +@define-color graptos_dialog_output #bcbcbc; +@define-color graptos_git_output_bg #1c1c1c; +@define-color graptos_dialog_action #5f87af; +@define-color graptos_dialog_destructive_action #af5f5f; +@define-color graptos_dialog_input_fg #bcbcbc; +@define-color graptos_dialog_input_bg #1c1c1c; +@define-color graptos_search_match_bg #5f5f87; +@define-color graptos_search_match_fg #ffffff; +@define-color graptos_diagnostic_warning_bg #3a321f; +@define-color graptos_diagnostic_warning_fg #87875f; +@define-color graptos_codex_preview_bg #262626; +@define-color graptos_codex_preview_fg #bcbcbc; +@define-color graptos_codex_prompt_bg #1c1c1c; +/* @graptos-ui-font: Inconsolata */ +/* @graptos-editor-font: */ +/* @graptos-preview-font: */ +/* @graptos-terminal-font: */ +/* @graptos-code-font: monospace */ + +.graptos-root { background: @graptos_editor_bg; color: @graptos_editor_fg; } +.graptos-editor, .graptos-editor text, textview.graptos-editor, textview.graptos-editor text, sourceview.graptos-editor, sourceview.graptos-editor text { background: @graptos_editor_bg; color: @graptos_editor_fg; caret-color: @graptos_editor_cursor; } +sourceview.graptos-editor gutter, sourceview.graptos-editor gutter * { background: @graptos_editor_gutter_bg; color: @graptos_editor_gutter_fg; } +sourceview.graptos-editor text selection { background: @graptos_editor_selection_bg; color: @graptos_editor_selection_fg; } +.graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; } +.graptos-bottom, .graptos-search-panel, .graptos-tool-panel { background: @graptos_bottombar_bg; color: @graptos_bottombar_fg; } +.graptos-project-pane, .graptos-project-tree, .graptos-project-tree row { background: @graptos_sidebar_bg; color: @graptos_project_tree_fg; } +.graptos-root notebook > header, .graptos-root notebook > header.top > tabs > tab { background: @graptos_tabbar_bg; color: @graptos_tabbar_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tabbar_border; } +.graptos-root notebook > header.top { border-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-root notebook > header.top > tabs > tab:checked { background: @graptos_tab_active_bg; color: @graptos_tab_active_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; } +.graptos-root button { background: @graptos_button_bg; color: @graptos_button_fg; } +.graptos-root button:hover { background: @graptos_button_hover_bg; } +.graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; } +.graptos-root entry, .graptos-root entry text, .graptos-root spinbutton, .graptos-root spinbutton text { background: @graptos_input_bg; color: @graptos_input_fg; border-color: @graptos_input_border; } +popover.graptos-context-popover, popover.graptos-tools-popover, popover.graptos-completion-popover, popover.graptos-hover-popover { background: @graptos_popover_bg; border-color: @graptos_popover_border; } +.graptos-completion-list row:selected, .graptos-completion-list row:hover { background: @graptos_completion_selection_bg; color: @graptos_completion_selection_fg; } +.graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; } +.graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; } +.graptos-codex-approval { background: @graptos_diagnostic_warning_bg; border-color: @graptos_diagnostic_warning_fg; } +.graptos-codex-review { background: @graptos_search_match_bg; border-color: @graptos_search_match_fg; } +/* GRAPTOS THEME END */ diff --git a/data/themes/dracula.css b/data/themes/dracula.css new file mode 100644 index 0000000..3665049 --- /dev/null +++ b/data/themes/dracula.css @@ -0,0 +1,107 @@ +/* Graptoς CSS theme generated from dracula.ini. */ +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #282a36; +@define-color graptos_editor_fg #f8f8f2; +@define-color graptos_editor_gutter_bg #21222c; +@define-color graptos_editor_gutter_fg #6272a4; +@define-color graptos_editor_current_line_bg #44475a; +@define-color graptos_editor_selection_bg #44475a; +@define-color graptos_editor_selection_fg #f8f8f2; +@define-color graptos_editor_cursor #f8f8f2; +@define-color graptos_sidebar_bg #21222c; +@define-color graptos_tabbar_bg #21222c; +@define-color graptos_tabbar_fg #bd93f9; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #282a36; +@define-color graptos_tab_active_fg #f8f8f2; +@define-color graptos_tab_active_border #bd93f9; +@define-color graptos_tab_tiled_indicator #bd93f9; +@define-color graptos_topbar_bg #21222c; +@define-color graptos_topbar_fg #f8f8f2; +@define-color graptos_bottombar_bg #21222c; +@define-color graptos_bottombar_fg #f8f8f2; +@define-color graptos_status_error #ff5555; +@define-color graptos_button_bg #44475a; +@define-color graptos_button_fg #f8f8f2; +@define-color graptos_button_hover_bg #6272a4; +@define-color graptos_button_active_bg #bd93f9; +@define-color graptos_input_bg #282a36; +@define-color graptos_input_fg #f8f8f2; +@define-color graptos_input_border #6272a4; +@define-color graptos_project_tree_fg #f8f8f2; +@define-color graptos_project_tree_selected_bg #44475a; +@define-color graptos_project_tree_selected_fg #f8f8f2; +@define-color graptos_git_status_modified #f1fa8c; +@define-color graptos_git_status_added #50fa7b; +@define-color graptos_git_status_deleted #ff5555; +@define-color graptos_git_status_renamed #8be9fd; +@define-color graptos_git_status_conflict #ff79c6; +@define-color graptos_git_status_untracked #8be9fd; +@define-color graptos_git_status_staged #bd93f9; +@define-color graptos_scroll_preview_bg #21222c; +@define-color graptos_scroll_preview_fg #6272a4; +@define-color graptos_popover_bg #282a36; +@define-color graptos_popover_border #00000000; +@define-color graptos_tooltip_bg #282a36; +@define-color graptos_tooltip_fg #f8f8f2; +@define-color graptos_tooltip_border #00000000; +@define-color graptos_ref_popover_bg #282a36; +@define-color graptos_ref_popover_fg #f8f8f2; +@define-color graptos_ref_popover_heading #bd93f9; +@define-color graptos_ref_popover_title #8be9fd; +@define-color graptos_ref_popover_kind #ff79c6; +@define-color graptos_ref_popover_snippet #f8f8f2; +@define-color graptos_ref_popover_hover_bg #44475a; +@define-color graptos_ref_popover_hover_fg #f8f8f2; +@define-color graptos_completion_popover_bg #282a36; +@define-color graptos_completion_popover_fg #f8f8f2; +@define-color graptos_completion_popover_detail #6272a4; +@define-color graptos_completion_selection_bg #bd93f9; +@define-color graptos_completion_selection_fg #282a36; +@define-color graptos_dialog_bg #282a36; +@define-color graptos_dialog_fg #f8f8f2; +@define-color graptos_dialog_border #00000000; +@define-color graptos_dialog_title #f8f8f2; +@define-color graptos_dialog_body #f8f8f2; +@define-color graptos_dialog_muted #6272a4; +@define-color graptos_dialog_output #f8f8f2; +@define-color graptos_git_output_bg #21222c; +@define-color graptos_dialog_action #8be9fd; +@define-color graptos_dialog_destructive_action #ff5555; +@define-color graptos_dialog_input_fg #f8f8f2; +@define-color graptos_dialog_input_bg #21222c; +@define-color graptos_search_match_bg #6272a4; +@define-color graptos_search_match_fg #f8f8f2; +@define-color graptos_diagnostic_warning_bg #5f4b24; +@define-color graptos_diagnostic_warning_fg #f1fa8c; +@define-color graptos_codex_preview_bg #282a36; +@define-color graptos_codex_preview_fg #f8f8f2; +@define-color graptos_codex_prompt_bg #21222c; +/* @graptos-ui-font: Inconsolata */ +/* @graptos-editor-font: */ +/* @graptos-preview-font: */ +/* @graptos-terminal-font: */ +/* @graptos-code-font: monospace */ + +.graptos-root { background: @graptos_editor_bg; color: @graptos_editor_fg; } +.graptos-editor, .graptos-editor text, textview.graptos-editor, textview.graptos-editor text, sourceview.graptos-editor, sourceview.graptos-editor text { background: @graptos_editor_bg; color: @graptos_editor_fg; caret-color: @graptos_editor_cursor; } +sourceview.graptos-editor gutter, sourceview.graptos-editor gutter * { background: @graptos_editor_gutter_bg; color: @graptos_editor_gutter_fg; } +sourceview.graptos-editor text selection { background: @graptos_editor_selection_bg; color: @graptos_editor_selection_fg; } +.graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; } +.graptos-bottom, .graptos-search-panel, .graptos-tool-panel { background: @graptos_bottombar_bg; color: @graptos_bottombar_fg; } +.graptos-project-pane, .graptos-project-tree, .graptos-project-tree row { background: @graptos_sidebar_bg; color: @graptos_project_tree_fg; } +.graptos-root notebook > header, .graptos-root notebook > header.top > tabs > tab { background: @graptos_tabbar_bg; color: @graptos_tabbar_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tabbar_border; } +.graptos-root notebook > header.top { border-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-root notebook > header.top > tabs > tab:checked { background: @graptos_tab_active_bg; color: @graptos_tab_active_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; } +.graptos-root button { background: @graptos_button_bg; color: @graptos_button_fg; } +.graptos-root button:hover { background: @graptos_button_hover_bg; } +.graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; } +.graptos-root entry, .graptos-root entry text, .graptos-root spinbutton, .graptos-root spinbutton text { background: @graptos_input_bg; color: @graptos_input_fg; border-color: @graptos_input_border; } +popover.graptos-context-popover, popover.graptos-tools-popover, popover.graptos-completion-popover, popover.graptos-hover-popover { background: @graptos_popover_bg; border-color: @graptos_popover_border; } +.graptos-completion-list row:selected, .graptos-completion-list row:hover { background: @graptos_completion_selection_bg; color: @graptos_completion_selection_fg; } +.graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; } +.graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; } +.graptos-codex-approval { background: @graptos_diagnostic_warning_bg; border-color: @graptos_diagnostic_warning_fg; } +.graptos-codex-review { background: @graptos_search_match_bg; border-color: @graptos_search_match_fg; } +/* GRAPTOS THEME END */ diff --git a/data/themes/github.css b/data/themes/github.css new file mode 100644 index 0000000..116bb2f --- /dev/null +++ b/data/themes/github.css @@ -0,0 +1,107 @@ +/* Graptoς CSS theme generated from github.ini. */ +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #0d1117; +@define-color graptos_editor_fg #c9d1d9; +@define-color graptos_editor_gutter_bg #010409; +@define-color graptos_editor_gutter_fg #8b949e; +@define-color graptos_editor_current_line_bg #161b22; +@define-color graptos_editor_selection_bg #264f78; +@define-color graptos_editor_selection_fg #ffffff; +@define-color graptos_editor_cursor #c9d1d9; +@define-color graptos_sidebar_bg #010409; +@define-color graptos_tabbar_bg #010409; +@define-color graptos_tabbar_fg #8b949e; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #0d1117; +@define-color graptos_tab_active_fg #f0f6fc; +@define-color graptos_tab_active_border #1f6feb; +@define-color graptos_tab_tiled_indicator #1f6feb; +@define-color graptos_topbar_bg #010409; +@define-color graptos_topbar_fg #c9d1d9; +@define-color graptos_bottombar_bg #010409; +@define-color graptos_bottombar_fg #c9d1d9; +@define-color graptos_status_error #ff7b72; +@define-color graptos_button_bg #21262d; +@define-color graptos_button_fg #c9d1d9; +@define-color graptos_button_hover_bg #30363d; +@define-color graptos_button_active_bg #1f6feb; +@define-color graptos_input_bg #0d1117; +@define-color graptos_input_fg #c9d1d9; +@define-color graptos_input_border #30363d; +@define-color graptos_project_tree_fg #c9d1d9; +@define-color graptos_project_tree_selected_bg #161b22; +@define-color graptos_project_tree_selected_fg #f0f6fc; +@define-color graptos_git_status_modified #d29922; +@define-color graptos_git_status_added #3fb950; +@define-color graptos_git_status_deleted #ff7b72; +@define-color graptos_git_status_renamed #58a6ff; +@define-color graptos_git_status_conflict #bc8cff; +@define-color graptos_git_status_untracked #39c5cf; +@define-color graptos_git_status_staged #bc8cff; +@define-color graptos_scroll_preview_bg #010409; +@define-color graptos_scroll_preview_fg #484f58; +@define-color graptos_popover_bg #161b22; +@define-color graptos_popover_border #00000000; +@define-color graptos_tooltip_bg #161b22; +@define-color graptos_tooltip_fg #c9d1d9; +@define-color graptos_tooltip_border #00000000; +@define-color graptos_ref_popover_bg #161b22; +@define-color graptos_ref_popover_fg #c9d1d9; +@define-color graptos_ref_popover_heading #bc8cff; +@define-color graptos_ref_popover_title #58a6ff; +@define-color graptos_ref_popover_kind #8b949e; +@define-color graptos_ref_popover_snippet #c9d1d9; +@define-color graptos_ref_popover_hover_bg #21262d; +@define-color graptos_ref_popover_hover_fg #f0f6fc; +@define-color graptos_completion_popover_bg #161b22; +@define-color graptos_completion_popover_fg #c9d1d9; +@define-color graptos_completion_popover_detail #8b949e; +@define-color graptos_completion_selection_bg #1f6feb; +@define-color graptos_completion_selection_fg #ffffff; +@define-color graptos_dialog_bg #161b22; +@define-color graptos_dialog_fg #c9d1d9; +@define-color graptos_dialog_border #00000000; +@define-color graptos_dialog_title #f0f6fc; +@define-color graptos_dialog_body #c9d1d9; +@define-color graptos_dialog_muted #8b949e; +@define-color graptos_dialog_output #c9d1d9; +@define-color graptos_git_output_bg #0d1117; +@define-color graptos_dialog_action #58a6ff; +@define-color graptos_dialog_destructive_action #ff7b72; +@define-color graptos_dialog_input_fg #c9d1d9; +@define-color graptos_dialog_input_bg #0d1117; +@define-color graptos_search_match_bg #388bfd; +@define-color graptos_search_match_fg #ffffff; +@define-color graptos_diagnostic_warning_bg #3b2f12; +@define-color graptos_diagnostic_warning_fg #d29922; +@define-color graptos_codex_preview_bg #0d1117; +@define-color graptos_codex_preview_fg #c9d1d9; +@define-color graptos_codex_prompt_bg #010409; +/* @graptos-ui-font: Inconsolata */ +/* @graptos-editor-font: */ +/* @graptos-preview-font: */ +/* @graptos-terminal-font: */ +/* @graptos-code-font: monospace */ + +.graptos-root { background: @graptos_editor_bg; color: @graptos_editor_fg; } +.graptos-editor, .graptos-editor text, textview.graptos-editor, textview.graptos-editor text, sourceview.graptos-editor, sourceview.graptos-editor text { background: @graptos_editor_bg; color: @graptos_editor_fg; caret-color: @graptos_editor_cursor; } +sourceview.graptos-editor gutter, sourceview.graptos-editor gutter * { background: @graptos_editor_gutter_bg; color: @graptos_editor_gutter_fg; } +sourceview.graptos-editor text selection { background: @graptos_editor_selection_bg; color: @graptos_editor_selection_fg; } +.graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; } +.graptos-bottom, .graptos-search-panel, .graptos-tool-panel { background: @graptos_bottombar_bg; color: @graptos_bottombar_fg; } +.graptos-project-pane, .graptos-project-tree, .graptos-project-tree row { background: @graptos_sidebar_bg; color: @graptos_project_tree_fg; } +.graptos-root notebook > header, .graptos-root notebook > header.top > tabs > tab { background: @graptos_tabbar_bg; color: @graptos_tabbar_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tabbar_border; } +.graptos-root notebook > header.top { border-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-root notebook > header.top > tabs > tab:checked { background: @graptos_tab_active_bg; color: @graptos_tab_active_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; } +.graptos-root button { background: @graptos_button_bg; color: @graptos_button_fg; } +.graptos-root button:hover { background: @graptos_button_hover_bg; } +.graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; } +.graptos-root entry, .graptos-root entry text, .graptos-root spinbutton, .graptos-root spinbutton text { background: @graptos_input_bg; color: @graptos_input_fg; border-color: @graptos_input_border; } +popover.graptos-context-popover, popover.graptos-tools-popover, popover.graptos-completion-popover, popover.graptos-hover-popover { background: @graptos_popover_bg; border-color: @graptos_popover_border; } +.graptos-completion-list row:selected, .graptos-completion-list row:hover { background: @graptos_completion_selection_bg; color: @graptos_completion_selection_fg; } +.graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; } +.graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; } +.graptos-codex-approval { background: @graptos_diagnostic_warning_bg; border-color: @graptos_diagnostic_warning_fg; } +.graptos-codex-review { background: @graptos_search_match_bg; border-color: @graptos_search_match_fg; } +/* GRAPTOS THEME END */ diff --git a/data/themes/graptos.css b/data/themes/graptos.css new file mode 100644 index 0000000..f3e003c --- /dev/null +++ b/data/themes/graptos.css @@ -0,0 +1,151 @@ +/* + * Graptoς editable CSS theme. + * + * Copy this file to ~/.config/graptos/theme.css or load it from the Theme + * dialog. Graptoς loads theme CSS at runtime, so every rule here can be edited + * without rebuilding the application. + */ + +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #181a1f; +@define-color graptos_editor_fg #d4d4d4; +@define-color graptos_sidebar_bg #111318; +@define-color graptos_tabbar_bg #111318; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #20232b; +@define-color graptos_tab_active_fg #ffffff; +@define-color graptos_tab_active_border #89b4fa; +@define-color graptos_tab_tiled_indicator #89b4fa; +@define-color graptos_topbar_bg #111318; +@define-color graptos_bottombar_bg #111318; +@define-color graptos_popover_bg #1b1f24; +@define-color graptos_dialog_bg #1b1f24; +@define-color graptos_accent #89b4fa; +@define-color graptos_warning_bg #5f4b24; + +window.graptos-window, +window.graptos-window > contents, +.graptos-root, +.graptos-root > box, +.graptos-tab-page, +.graptos-editor-area, +.graptos-editor-overlay, +.graptos-editor-content { + background: @graptos_editor_bg; + background-color: @graptos_editor_bg; + color: @graptos_editor_fg; +} + +.graptos-editor, +.graptos-editor text, +textview.graptos-editor, +textview.graptos-editor text, +sourceview.graptos-editor, +sourceview.graptos-editor text { + background: @graptos_editor_bg; + background-color: @graptos_editor_bg; + color: @graptos_editor_fg; +} + +.graptos-project-pane, +.graptos-project-tree, +.graptos-project-tree row { + background: @graptos_sidebar_bg; + background-color: @graptos_sidebar_bg; +} + +.graptos-top { + background: @graptos_topbar_bg; + background-color: @graptos_topbar_bg; +} + +.graptos-bottom, +.graptos-search-panel, +.graptos-tool-panel { + background: @graptos_bottombar_bg; + background-color: @graptos_bottombar_bg; +} + +.graptos-root notebook > header, +.graptos-root notebook > header.top > tabs > tab { + background: @graptos_tabbar_bg; + background-color: @graptos_tabbar_bg; + border-left-color: @graptos_tabbar_border; + border-right-color: @graptos_tabbar_border; + border-top-color: @graptos_tabbar_border; + border-bottom-color: @graptos_tabbar_border; +} + +.graptos-root notebook > header.top { + border-color: @graptos_tabbar_border; + border-bottom-color: @graptos_tab_active_border; +} + +.graptos-root notebook > header.top > tabs > tab:checked { + background: @graptos_tab_active_bg; + background-color: @graptos_tab_active_bg; + color: @graptos_tab_active_fg; + border-left-color: @graptos_tabbar_border; + border-right-color: @graptos_tabbar_border; + border-top-color: @graptos_tabbar_border; + border-bottom-color: @graptos_tab_active_border; +} + +.graptos-tab-tiled { + box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; +} + +.graptos-preview, +.graptos-minimap, +.graptos-codex-preview { + background: @graptos_editor_bg; + background-color: @graptos_editor_bg; + color: @graptos_editor_fg; +} + +popover.graptos-context-popover, +popover.graptos-tools-popover, +popover.graptos-completion-popover, +popover.graptos-hover-popover, +popover.graptos-context-popover contents, +popover.graptos-tools-popover contents, +popover.graptos-completion-popover contents, +popover.graptos-hover-popover contents { + background: @graptos_popover_bg; + background-color: @graptos_popover_bg; +} + +.graptos-completion-list row:selected, +.graptos-completion-list row:selected label, +.graptos-completion-list row:selected .graptos-completion-title, +.graptos-completion-list row:selected .graptos-completion-detail { + background: @graptos_accent; + background-color: @graptos_accent; +} + +window.graptos-window .graptos-root.graptos-dialog-root, +window.graptos-window .graptos-dialog-output, +window.graptos-window .graptos-dialog-output text { + background: @graptos_dialog_bg; + background-color: @graptos_dialog_bg; + color: @graptos_editor_fg; +} + +.graptos-search-match { + background: @graptos_accent; +} + +.graptos-diagnostic-warning { + background: @graptos_warning_bg; +} + +.graptos-codex-approval { + background: @graptos_warning_bg; + border-color: @graptos_editor_fg; +} + +.graptos-codex-review { + background: @graptos_accent; + border-color: @graptos_editor_fg; +} +/* GRAPTOS THEME END */ diff --git a/data/themes/gruvbox.css b/data/themes/gruvbox.css new file mode 100644 index 0000000..8786f00 --- /dev/null +++ b/data/themes/gruvbox.css @@ -0,0 +1,107 @@ +/* Graptoς CSS theme generated from gruvbox.ini. */ +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #282828; +@define-color graptos_editor_fg #ebdbb2; +@define-color graptos_editor_gutter_bg #1d2021; +@define-color graptos_editor_gutter_fg #928374; +@define-color graptos_editor_current_line_bg #32302f; +@define-color graptos_editor_selection_bg #504945; +@define-color graptos_editor_selection_fg #fbf1c7; +@define-color graptos_editor_cursor #ebdbb2; +@define-color graptos_sidebar_bg #1d2021; +@define-color graptos_tabbar_bg #1d2021; +@define-color graptos_tabbar_fg #a89984; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #282828; +@define-color graptos_tab_active_fg #fbf1c7; +@define-color graptos_tab_active_border #458588; +@define-color graptos_tab_tiled_indicator #458588; +@define-color graptos_topbar_bg #1d2021; +@define-color graptos_topbar_fg #ebdbb2; +@define-color graptos_bottombar_bg #1d2021; +@define-color graptos_bottombar_fg #ebdbb2; +@define-color graptos_status_error #fb4934; +@define-color graptos_button_bg #3c3836; +@define-color graptos_button_fg #ebdbb2; +@define-color graptos_button_hover_bg #504945; +@define-color graptos_button_active_bg #458588; +@define-color graptos_input_bg #1d2021; +@define-color graptos_input_fg #ebdbb2; +@define-color graptos_input_border #504945; +@define-color graptos_project_tree_fg #ebdbb2; +@define-color graptos_project_tree_selected_bg #3c3836; +@define-color graptos_project_tree_selected_fg #fbf1c7; +@define-color graptos_git_status_modified #fabd2f; +@define-color graptos_git_status_added #b8bb26; +@define-color graptos_git_status_deleted #fb4934; +@define-color graptos_git_status_renamed #83a598; +@define-color graptos_git_status_conflict #d3869b; +@define-color graptos_git_status_untracked #8ec07c; +@define-color graptos_git_status_staged #b16286; +@define-color graptos_scroll_preview_bg #1d2021; +@define-color graptos_scroll_preview_fg #665c54; +@define-color graptos_popover_bg #282828; +@define-color graptos_popover_border #00000000; +@define-color graptos_tooltip_bg #282828; +@define-color graptos_tooltip_fg #ebdbb2; +@define-color graptos_tooltip_border #00000000; +@define-color graptos_ref_popover_bg #282828; +@define-color graptos_ref_popover_fg #ebdbb2; +@define-color graptos_ref_popover_heading #d3869b; +@define-color graptos_ref_popover_title #83a598; +@define-color graptos_ref_popover_kind #a89984; +@define-color graptos_ref_popover_snippet #ebdbb2; +@define-color graptos_ref_popover_hover_bg #3c3836; +@define-color graptos_ref_popover_hover_fg #fbf1c7; +@define-color graptos_completion_popover_bg #282828; +@define-color graptos_completion_popover_fg #ebdbb2; +@define-color graptos_completion_popover_detail #a89984; +@define-color graptos_completion_selection_bg #458588; +@define-color graptos_completion_selection_fg #fbf1c7; +@define-color graptos_dialog_bg #282828; +@define-color graptos_dialog_fg #ebdbb2; +@define-color graptos_dialog_border #00000000; +@define-color graptos_dialog_title #fbf1c7; +@define-color graptos_dialog_body #ebdbb2; +@define-color graptos_dialog_muted #a89984; +@define-color graptos_dialog_output #ebdbb2; +@define-color graptos_git_output_bg #1d2021; +@define-color graptos_dialog_action #83a598; +@define-color graptos_dialog_destructive_action #fb4934; +@define-color graptos_dialog_input_fg #ebdbb2; +@define-color graptos_dialog_input_bg #1d2021; +@define-color graptos_search_match_bg #665c54; +@define-color graptos_search_match_fg #fbf1c7; +@define-color graptos_diagnostic_warning_bg #3c2f1c; +@define-color graptos_diagnostic_warning_fg #fabd2f; +@define-color graptos_codex_preview_bg #282828; +@define-color graptos_codex_preview_fg #ebdbb2; +@define-color graptos_codex_prompt_bg #1d2021; +/* @graptos-ui-font: Inconsolata */ +/* @graptos-editor-font: */ +/* @graptos-preview-font: */ +/* @graptos-terminal-font: */ +/* @graptos-code-font: monospace */ + +.graptos-root { background: @graptos_editor_bg; color: @graptos_editor_fg; } +.graptos-editor, .graptos-editor text, textview.graptos-editor, textview.graptos-editor text, sourceview.graptos-editor, sourceview.graptos-editor text { background: @graptos_editor_bg; color: @graptos_editor_fg; caret-color: @graptos_editor_cursor; } +sourceview.graptos-editor gutter, sourceview.graptos-editor gutter * { background: @graptos_editor_gutter_bg; color: @graptos_editor_gutter_fg; } +sourceview.graptos-editor text selection { background: @graptos_editor_selection_bg; color: @graptos_editor_selection_fg; } +.graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; } +.graptos-bottom, .graptos-search-panel, .graptos-tool-panel { background: @graptos_bottombar_bg; color: @graptos_bottombar_fg; } +.graptos-project-pane, .graptos-project-tree, .graptos-project-tree row { background: @graptos_sidebar_bg; color: @graptos_project_tree_fg; } +.graptos-root notebook > header, .graptos-root notebook > header.top > tabs > tab { background: @graptos_tabbar_bg; color: @graptos_tabbar_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tabbar_border; } +.graptos-root notebook > header.top { border-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-root notebook > header.top > tabs > tab:checked { background: @graptos_tab_active_bg; color: @graptos_tab_active_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; } +.graptos-root button { background: @graptos_button_bg; color: @graptos_button_fg; } +.graptos-root button:hover { background: @graptos_button_hover_bg; } +.graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; } +.graptos-root entry, .graptos-root entry text, .graptos-root spinbutton, .graptos-root spinbutton text { background: @graptos_input_bg; color: @graptos_input_fg; border-color: @graptos_input_border; } +popover.graptos-context-popover, popover.graptos-tools-popover, popover.graptos-completion-popover, popover.graptos-hover-popover { background: @graptos_popover_bg; border-color: @graptos_popover_border; } +.graptos-completion-list row:selected, .graptos-completion-list row:hover { background: @graptos_completion_selection_bg; color: @graptos_completion_selection_fg; } +.graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; } +.graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; } +.graptos-codex-approval { background: @graptos_diagnostic_warning_bg; border-color: @graptos_diagnostic_warning_fg; } +.graptos-codex-review { background: @graptos_search_match_bg; border-color: @graptos_search_match_fg; } +/* GRAPTOS THEME END */ diff --git a/data/themes/moonfly.css b/data/themes/moonfly.css new file mode 100644 index 0000000..922e101 --- /dev/null +++ b/data/themes/moonfly.css @@ -0,0 +1,107 @@ +/* Graptoς CSS theme generated from moonfly.ini. */ +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #080808; +@define-color graptos_editor_fg #bdbdbd; +@define-color graptos_editor_gutter_bg #0f0f0f; +@define-color graptos_editor_gutter_fg #626262; +@define-color graptos_editor_current_line_bg #1c1c1c; +@define-color graptos_editor_selection_bg #b2ceee; +@define-color graptos_editor_selection_fg #080808; +@define-color graptos_editor_cursor #e4e4e4; +@define-color graptos_sidebar_bg #0f0f0f; +@define-color graptos_tabbar_bg #0f0f0f; +@define-color graptos_tabbar_fg #949494; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #080808; +@define-color graptos_tab_active_fg #e4e4e4; +@define-color graptos_tab_active_border #80a0ff; +@define-color graptos_tab_tiled_indicator #80a0ff; +@define-color graptos_topbar_bg #0f0f0f; +@define-color graptos_topbar_fg #bdbdbd; +@define-color graptos_bottombar_bg #0f0f0f; +@define-color graptos_bottombar_fg #bdbdbd; +@define-color graptos_status_error #ff5454; +@define-color graptos_button_bg #080808; +@define-color graptos_button_fg #bdbdbd; +@define-color graptos_button_hover_bg #1c1c1c; +@define-color graptos_button_active_bg #323437; +@define-color graptos_input_bg #080808; +@define-color graptos_input_fg #bdbdbd; +@define-color graptos_input_border #323437; +@define-color graptos_project_tree_fg #bdbdbd; +@define-color graptos_project_tree_selected_bg #1c1c1c; +@define-color graptos_project_tree_selected_fg #e4e4e4; +@define-color graptos_git_status_modified #e3c78a; +@define-color graptos_git_status_added #8cc85f; +@define-color graptos_git_status_deleted #ff5454; +@define-color graptos_git_status_renamed #80a0ff; +@define-color graptos_git_status_conflict #cf87e8; +@define-color graptos_git_status_untracked #79dac8; +@define-color graptos_git_status_staged #ae81ff; +@define-color graptos_scroll_preview_bg #0f0f0f; +@define-color graptos_scroll_preview_fg #626262; +@define-color graptos_popover_bg #080808; +@define-color graptos_popover_border #00000000; +@define-color graptos_tooltip_bg #080808; +@define-color graptos_tooltip_fg #bdbdbd; +@define-color graptos_tooltip_border #00000000; +@define-color graptos_ref_popover_bg #080808; +@define-color graptos_ref_popover_fg #bdbdbd; +@define-color graptos_ref_popover_heading #cf87e8; +@define-color graptos_ref_popover_title #79dac8; +@define-color graptos_ref_popover_kind #949494; +@define-color graptos_ref_popover_snippet #c6c6c6; +@define-color graptos_ref_popover_hover_bg #1c1c1c; +@define-color graptos_ref_popover_hover_fg #e4e4e4; +@define-color graptos_completion_popover_bg #080808; +@define-color graptos_completion_popover_fg #bdbdbd; +@define-color graptos_completion_popover_detail #949494; +@define-color graptos_completion_selection_bg #80a0ff; +@define-color graptos_completion_selection_fg #080808; +@define-color graptos_dialog_bg #080808; +@define-color graptos_dialog_fg #bdbdbd; +@define-color graptos_dialog_border #00000000; +@define-color graptos_dialog_title #e4e4e4; +@define-color graptos_dialog_body #bdbdbd; +@define-color graptos_dialog_muted #949494; +@define-color graptos_dialog_output #c6c6c6; +@define-color graptos_git_output_bg #0f0f0f; +@define-color graptos_dialog_action #79dac8; +@define-color graptos_dialog_destructive_action #ff5454; +@define-color graptos_dialog_input_fg #bdbdbd; +@define-color graptos_dialog_input_bg #080808; +@define-color graptos_search_match_bg #323437; +@define-color graptos_search_match_fg #e4e4e4; +@define-color graptos_diagnostic_warning_bg #2f281d; +@define-color graptos_diagnostic_warning_fg #e3c78a; +@define-color graptos_codex_preview_bg #080808; +@define-color graptos_codex_preview_fg #bdbdbd; +@define-color graptos_codex_prompt_bg #080808; +/* @graptos-ui-font: Inconsolata */ +/* @graptos-editor-font: */ +/* @graptos-preview-font: */ +/* @graptos-terminal-font: */ +/* @graptos-code-font: monospace */ + +.graptos-root { background: @graptos_editor_bg; color: @graptos_editor_fg; } +.graptos-editor, .graptos-editor text, textview.graptos-editor, textview.graptos-editor text, sourceview.graptos-editor, sourceview.graptos-editor text { background: @graptos_editor_bg; color: @graptos_editor_fg; caret-color: @graptos_editor_cursor; } +sourceview.graptos-editor gutter, sourceview.graptos-editor gutter * { background: @graptos_editor_gutter_bg; color: @graptos_editor_gutter_fg; } +sourceview.graptos-editor text selection { background: @graptos_editor_selection_bg; color: @graptos_editor_selection_fg; } +.graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; } +.graptos-bottom, .graptos-search-panel, .graptos-tool-panel { background: @graptos_bottombar_bg; color: @graptos_bottombar_fg; } +.graptos-project-pane, .graptos-project-tree, .graptos-project-tree row { background: @graptos_sidebar_bg; color: @graptos_project_tree_fg; } +.graptos-root notebook > header, .graptos-root notebook > header.top > tabs > tab { background: @graptos_tabbar_bg; color: @graptos_tabbar_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tabbar_border; } +.graptos-root notebook > header.top { border-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-root notebook > header.top > tabs > tab:checked { background: @graptos_tab_active_bg; color: @graptos_tab_active_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; } +.graptos-root button { background: @graptos_button_bg; color: @graptos_button_fg; } +.graptos-root button:hover { background: @graptos_button_hover_bg; } +.graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; } +.graptos-root entry, .graptos-root entry text, .graptos-root spinbutton, .graptos-root spinbutton text { background: @graptos_input_bg; color: @graptos_input_fg; border-color: @graptos_input_border; } +popover.graptos-context-popover, popover.graptos-tools-popover, popover.graptos-completion-popover, popover.graptos-hover-popover { background: @graptos_popover_bg; border-color: @graptos_popover_border; } +.graptos-completion-list row:selected, .graptos-completion-list row:hover { background: @graptos_completion_selection_bg; color: @graptos_completion_selection_fg; } +.graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; } +.graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; } +.graptos-codex-approval { background: @graptos_diagnostic_warning_bg; border-color: @graptos_diagnostic_warning_fg; } +.graptos-codex-review { background: @graptos_search_match_bg; border-color: @graptos_search_match_fg; } +/* GRAPTOS THEME END */ diff --git a/data/themes/srcery.css b/data/themes/srcery.css new file mode 100644 index 0000000..e0ec96a --- /dev/null +++ b/data/themes/srcery.css @@ -0,0 +1,107 @@ +/* Graptoς CSS theme generated from srcery.ini. */ +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #1c1b19; +@define-color graptos_editor_fg #fce8c3; +@define-color graptos_editor_gutter_bg #121212; +@define-color graptos_editor_gutter_fg #918175; +@define-color graptos_editor_current_line_bg #2d2b28; +@define-color graptos_editor_selection_bg #3a3935; +@define-color graptos_editor_selection_fg #fce8c3; +@define-color graptos_editor_cursor #fce8c3; +@define-color graptos_sidebar_bg #121212; +@define-color graptos_tabbar_bg #121212; +@define-color graptos_tabbar_fg #918175; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #1c1b19; +@define-color graptos_tab_active_fg #fce8c3; +@define-color graptos_tab_active_border #2c78bf; +@define-color graptos_tab_tiled_indicator #2c78bf; +@define-color graptos_topbar_bg #121212; +@define-color graptos_topbar_fg #fce8c3; +@define-color graptos_bottombar_bg #121212; +@define-color graptos_bottombar_fg #fce8c3; +@define-color graptos_status_error #ef2f27; +@define-color graptos_button_bg #2d2b28; +@define-color graptos_button_fg #fce8c3; +@define-color graptos_button_hover_bg #3a3935; +@define-color graptos_button_active_bg #2c78bf; +@define-color graptos_input_bg #121212; +@define-color graptos_input_fg #fce8c3; +@define-color graptos_input_border #3a3935; +@define-color graptos_project_tree_fg #fce8c3; +@define-color graptos_project_tree_selected_bg #2d2b28; +@define-color graptos_project_tree_selected_fg #ffffff; +@define-color graptos_git_status_modified #fbb829; +@define-color graptos_git_status_added #519f50; +@define-color graptos_git_status_deleted #ef2f27; +@define-color graptos_git_status_renamed #2c78bf; +@define-color graptos_git_status_conflict #e02c6d; +@define-color graptos_git_status_untracked #0aaeb3; +@define-color graptos_git_status_staged #e02c6d; +@define-color graptos_scroll_preview_bg #121212; +@define-color graptos_scroll_preview_fg #918175; +@define-color graptos_popover_bg #1c1b19; +@define-color graptos_popover_border #00000000; +@define-color graptos_tooltip_bg #1c1b19; +@define-color graptos_tooltip_fg #fce8c3; +@define-color graptos_tooltip_border #00000000; +@define-color graptos_ref_popover_bg #1c1b19; +@define-color graptos_ref_popover_fg #fce8c3; +@define-color graptos_ref_popover_heading #e02c6d; +@define-color graptos_ref_popover_title #0aaeb3; +@define-color graptos_ref_popover_kind #918175; +@define-color graptos_ref_popover_snippet #fce8c3; +@define-color graptos_ref_popover_hover_bg #2d2b28; +@define-color graptos_ref_popover_hover_fg #ffffff; +@define-color graptos_completion_popover_bg #1c1b19; +@define-color graptos_completion_popover_fg #fce8c3; +@define-color graptos_completion_popover_detail #918175; +@define-color graptos_completion_selection_bg #2c78bf; +@define-color graptos_completion_selection_fg #ffffff; +@define-color graptos_dialog_bg #1c1b19; +@define-color graptos_dialog_fg #fce8c3; +@define-color graptos_dialog_border #00000000; +@define-color graptos_dialog_title #ffffff; +@define-color graptos_dialog_body #fce8c3; +@define-color graptos_dialog_muted #918175; +@define-color graptos_dialog_output #fce8c3; +@define-color graptos_git_output_bg #121212; +@define-color graptos_dialog_action #0aaeb3; +@define-color graptos_dialog_destructive_action #ef2f27; +@define-color graptos_dialog_input_fg #fce8c3; +@define-color graptos_dialog_input_bg #121212; +@define-color graptos_search_match_bg #3a3935; +@define-color graptos_search_match_fg #ffffff; +@define-color graptos_diagnostic_warning_bg #3d2e14; +@define-color graptos_diagnostic_warning_fg #fbb829; +@define-color graptos_codex_preview_bg #1c1b19; +@define-color graptos_codex_preview_fg #fce8c3; +@define-color graptos_codex_prompt_bg #121212; +/* @graptos-ui-font: Inconsolata */ +/* @graptos-editor-font: */ +/* @graptos-preview-font: */ +/* @graptos-terminal-font: */ +/* @graptos-code-font: monospace */ + +.graptos-root { background: @graptos_editor_bg; color: @graptos_editor_fg; } +.graptos-editor, .graptos-editor text, textview.graptos-editor, textview.graptos-editor text, sourceview.graptos-editor, sourceview.graptos-editor text { background: @graptos_editor_bg; color: @graptos_editor_fg; caret-color: @graptos_editor_cursor; } +sourceview.graptos-editor gutter, sourceview.graptos-editor gutter * { background: @graptos_editor_gutter_bg; color: @graptos_editor_gutter_fg; } +sourceview.graptos-editor text selection { background: @graptos_editor_selection_bg; color: @graptos_editor_selection_fg; } +.graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; } +.graptos-bottom, .graptos-search-panel, .graptos-tool-panel { background: @graptos_bottombar_bg; color: @graptos_bottombar_fg; } +.graptos-project-pane, .graptos-project-tree, .graptos-project-tree row { background: @graptos_sidebar_bg; color: @graptos_project_tree_fg; } +.graptos-root notebook > header, .graptos-root notebook > header.top > tabs > tab { background: @graptos_tabbar_bg; color: @graptos_tabbar_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tabbar_border; } +.graptos-root notebook > header.top { border-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-root notebook > header.top > tabs > tab:checked { background: @graptos_tab_active_bg; color: @graptos_tab_active_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; } +.graptos-root button { background: @graptos_button_bg; color: @graptos_button_fg; } +.graptos-root button:hover { background: @graptos_button_hover_bg; } +.graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; } +.graptos-root entry, .graptos-root entry text, .graptos-root spinbutton, .graptos-root spinbutton text { background: @graptos_input_bg; color: @graptos_input_fg; border-color: @graptos_input_border; } +popover.graptos-context-popover, popover.graptos-tools-popover, popover.graptos-completion-popover, popover.graptos-hover-popover { background: @graptos_popover_bg; border-color: @graptos_popover_border; } +.graptos-completion-list row:selected, .graptos-completion-list row:hover { background: @graptos_completion_selection_bg; color: @graptos_completion_selection_fg; } +.graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; } +.graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; } +.graptos-codex-approval { background: @graptos_diagnostic_warning_bg; border-color: @graptos_diagnostic_warning_fg; } +.graptos-codex-review { background: @graptos_search_match_bg; border-color: @graptos_search_match_fg; } +/* GRAPTOS THEME END */ diff --git a/data/themes/tokyonight.css b/data/themes/tokyonight.css new file mode 100644 index 0000000..d4a409c --- /dev/null +++ b/data/themes/tokyonight.css @@ -0,0 +1,107 @@ +/* Graptoς CSS theme generated from tokyonight.ini. */ +/* GRAPTOS THEME BEGIN */ +@define-color graptos_editor_bg #1a1b26; +@define-color graptos_editor_fg #c0caf5; +@define-color graptos_editor_gutter_bg #15161e; +@define-color graptos_editor_gutter_fg #565f89; +@define-color graptos_editor_current_line_bg #24283b; +@define-color graptos_editor_selection_bg #33467c; +@define-color graptos_editor_selection_fg #c0caf5; +@define-color graptos_editor_cursor #c0caf5; +@define-color graptos_sidebar_bg #15161e; +@define-color graptos_tabbar_bg #15161e; +@define-color graptos_tabbar_fg #a9b1d6; +@define-color graptos_tabbar_border #00000000; +@define-color graptos_tab_active_bg #1a1b26; +@define-color graptos_tab_active_fg #c0caf5; +@define-color graptos_tab_active_border #7aa2f7; +@define-color graptos_tab_tiled_indicator #7aa2f7; +@define-color graptos_topbar_bg #15161e; +@define-color graptos_topbar_fg #c0caf5; +@define-color graptos_bottombar_bg #15161e; +@define-color graptos_bottombar_fg #c0caf5; +@define-color graptos_status_error #f7768e; +@define-color graptos_button_bg #24283b; +@define-color graptos_button_fg #c0caf5; +@define-color graptos_button_hover_bg #414868; +@define-color graptos_button_active_bg #7aa2f7; +@define-color graptos_input_bg #15161e; +@define-color graptos_input_fg #c0caf5; +@define-color graptos_input_border #414868; +@define-color graptos_project_tree_fg #c0caf5; +@define-color graptos_project_tree_selected_bg #24283b; +@define-color graptos_project_tree_selected_fg #ffffff; +@define-color graptos_git_status_modified #e0af68; +@define-color graptos_git_status_added #9ece6a; +@define-color graptos_git_status_deleted #f7768e; +@define-color graptos_git_status_renamed #7aa2f7; +@define-color graptos_git_status_conflict #bb9af7; +@define-color graptos_git_status_untracked #7dcfff; +@define-color graptos_git_status_staged #bb9af7; +@define-color graptos_scroll_preview_bg #15161e; +@define-color graptos_scroll_preview_fg #414868; +@define-color graptos_popover_bg #1a1b26; +@define-color graptos_popover_border #00000000; +@define-color graptos_tooltip_bg #1a1b26; +@define-color graptos_tooltip_fg #c0caf5; +@define-color graptos_tooltip_border #00000000; +@define-color graptos_ref_popover_bg #1a1b26; +@define-color graptos_ref_popover_fg #c0caf5; +@define-color graptos_ref_popover_heading #bb9af7; +@define-color graptos_ref_popover_title #7dcfff; +@define-color graptos_ref_popover_kind #a9b1d6; +@define-color graptos_ref_popover_snippet #c0caf5; +@define-color graptos_ref_popover_hover_bg #24283b; +@define-color graptos_ref_popover_hover_fg #ffffff; +@define-color graptos_completion_popover_bg #1a1b26; +@define-color graptos_completion_popover_fg #c0caf5; +@define-color graptos_completion_popover_detail #a9b1d6; +@define-color graptos_completion_selection_bg #7aa2f7; +@define-color graptos_completion_selection_fg #15161e; +@define-color graptos_dialog_bg #1a1b26; +@define-color graptos_dialog_fg #c0caf5; +@define-color graptos_dialog_border #00000000; +@define-color graptos_dialog_title #ffffff; +@define-color graptos_dialog_body #c0caf5; +@define-color graptos_dialog_muted #a9b1d6; +@define-color graptos_dialog_output #c0caf5; +@define-color graptos_git_output_bg #15161e; +@define-color graptos_dialog_action #7dcfff; +@define-color graptos_dialog_destructive_action #f7768e; +@define-color graptos_dialog_input_fg #c0caf5; +@define-color graptos_dialog_input_bg #15161e; +@define-color graptos_search_match_bg #33467c; +@define-color graptos_search_match_fg #ffffff; +@define-color graptos_diagnostic_warning_bg #3b2d1f; +@define-color graptos_diagnostic_warning_fg #e0af68; +@define-color graptos_codex_preview_bg #1a1b26; +@define-color graptos_codex_preview_fg #c0caf5; +@define-color graptos_codex_prompt_bg #15161e; +/* @graptos-ui-font: Inconsolata */ +/* @graptos-editor-font: */ +/* @graptos-preview-font: */ +/* @graptos-terminal-font: */ +/* @graptos-code-font: monospace */ + +.graptos-root { background: @graptos_editor_bg; color: @graptos_editor_fg; } +.graptos-editor, .graptos-editor text, textview.graptos-editor, textview.graptos-editor text, sourceview.graptos-editor, sourceview.graptos-editor text { background: @graptos_editor_bg; color: @graptos_editor_fg; caret-color: @graptos_editor_cursor; } +sourceview.graptos-editor gutter, sourceview.graptos-editor gutter * { background: @graptos_editor_gutter_bg; color: @graptos_editor_gutter_fg; } +sourceview.graptos-editor text selection { background: @graptos_editor_selection_bg; color: @graptos_editor_selection_fg; } +.graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; } +.graptos-bottom, .graptos-search-panel, .graptos-tool-panel { background: @graptos_bottombar_bg; color: @graptos_bottombar_fg; } +.graptos-project-pane, .graptos-project-tree, .graptos-project-tree row { background: @graptos_sidebar_bg; color: @graptos_project_tree_fg; } +.graptos-root notebook > header, .graptos-root notebook > header.top > tabs > tab { background: @graptos_tabbar_bg; color: @graptos_tabbar_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tabbar_border; } +.graptos-root notebook > header.top { border-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-root notebook > header.top > tabs > tab:checked { background: @graptos_tab_active_bg; color: @graptos_tab_active_fg; border-left-color: @graptos_tabbar_border; border-right-color: @graptos_tabbar_border; border-top-color: @graptos_tabbar_border; border-bottom-color: @graptos_tab_active_border; } +.graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; } +.graptos-root button { background: @graptos_button_bg; color: @graptos_button_fg; } +.graptos-root button:hover { background: @graptos_button_hover_bg; } +.graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; } +.graptos-root entry, .graptos-root entry text, .graptos-root spinbutton, .graptos-root spinbutton text { background: @graptos_input_bg; color: @graptos_input_fg; border-color: @graptos_input_border; } +popover.graptos-context-popover, popover.graptos-tools-popover, popover.graptos-completion-popover, popover.graptos-hover-popover { background: @graptos_popover_bg; border-color: @graptos_popover_border; } +.graptos-completion-list row:selected, .graptos-completion-list row:hover { background: @graptos_completion_selection_bg; color: @graptos_completion_selection_fg; } +.graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; } +.graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; } +.graptos-codex-approval { background: @graptos_diagnostic_warning_bg; border-color: @graptos_diagnostic_warning_fg; } +.graptos-codex-review { background: @graptos_search_match_bg; border-color: @graptos_search_match_fg; } +/* GRAPTOS THEME END */ diff --git a/src/app.h b/src/app.h index d715edc..1bb931b 100644 --- a/src/app.h +++ b/src/app.h @@ -91,6 +91,7 @@ typedef struct _EditorWindow { char *preview_font; /**< Preview font description, or empty for inherited preview default. */ char *terminal_font; /**< Terminal font description, or empty for VTE default. */ char *code_font; /**< Code snippet and auxiliary code font description. */ + char *theme_css_path; /**< Active CSS theme path. */ char *editor_bg_color; /**< Editor bg color. */ char *editor_fg_color; /**< Editor fg color. */ char *editor_gutter_bg_color; /**< Editor gutter bg color. */ @@ -102,8 +103,11 @@ typedef struct _EditorWindow { char *sidebar_bg_color; /**< Sidebar bg color. */ char *tabbar_bg_color; /**< Tabbar bg color. */ char *tabbar_fg_color; /**< Tabbar fg color. */ + char *tabbar_border_color; /**< Tabbar border color. */ char *tab_active_bg_color; /**< Tab active bg color. */ char *tab_active_fg_color; /**< Tab active fg color. */ + char *tab_active_border_color; /**< Tab active underline color. */ + char *tab_tiled_indicator_color; /**< Tiled tab indicator color. */ char *topbar_bg_color; /**< Topbar bg color. */ char *topbar_fg_color; /**< Topbar fg color. */ char *bottombar_bg_color; /**< Bottombar bg color. */ diff --git a/src/app/app_about_view_actions.inc.c b/src/app/app_about_view_actions.inc.c index 3810e8c..f402402 100644 --- a/src/app/app_about_view_actions.inc.c +++ b/src/app/app_about_view_actions.inc.c @@ -311,14 +311,14 @@ void action_shortcuts(GtkWidget *widget, gpointer user_data) { */ #define GRAPTOS_THEME_RESPONSE_KEEP_EDITING 1003 /** - * @brief Theme editor response code for reloading config.ini from disk. + * @brief Theme editor response code for reloading the saved theme from disk. */ #define GRAPTOS_THEME_RESPONSE_RELOAD_CONFIG 1004 /** * @brief Bundled theme preset shown in the Theme dialog. - * @details Presets are stored as normal Graptoς config fragments so loading a - * theme uses the same keys as manual `config.ini` editing. + * @details Presets are CSS files. Legacy INI files are still accepted by the + * loader only as import input for the active CSS theme. */ typedef struct { const char *label; /**< Human-readable preset name. */ @@ -329,13 +329,13 @@ typedef struct { * @brief Bundled theme presets available without network access. */ static const ThemePreset GRAPTOS_THEME_PRESETS[] = { - { "Dracula", "dracula.ini" }, - { "GitHub Dark", "github.ini" }, - { "Apprentice", "apprentice.ini" }, - { "Gruvbox Dark", "gruvbox.ini" }, - { "Tokyo Night", "tokyonight.ini" }, - { "Srcery", "srcery.ini" }, - { "Moonfly", "moonfly.ini" }, + { "Dracula", "dracula.css" }, + { "GitHub Dark", "github.css" }, + { "Apprentice", "apprentice.css" }, + { "Gruvbox Dark", "gruvbox.css" }, + { "Tokyo Night", "tokyonight.css" }, + { "Srcery", "srcery.css" }, + { "Moonfly", "moonfly.css" }, }; /** @@ -356,8 +356,11 @@ typedef struct { char *sidebar_bg_color; /**< Sidebar background. */ char *tabbar_bg_color; /**< Tabbar background. */ char *tabbar_fg_color; /**< Tabbar foreground. */ + char *tabbar_border_color; /**< Tabbar border. */ char *tab_active_bg_color; /**< Active tab background. */ char *tab_active_fg_color; /**< Active tab foreground. */ + char *tab_active_border_color; /**< Active tab underline. */ + char *tab_tiled_indicator_color; /**< Tiled tab indicator. */ char *topbar_bg_color; /**< Topbar background. */ char *topbar_fg_color; /**< Topbar foreground. */ char *bottombar_bg_color; /**< Bottombar background. */ @@ -424,6 +427,7 @@ typedef struct { char *preview_font; /**< Preview font. */ char *terminal_font; /**< Terminal font. */ char *code_font; /**< Code/snippet font. */ + char *theme_css_path; /**< User CSS path loaded after generated theme CSS. */ } ThemeState; /** @@ -464,6 +468,15 @@ typedef struct { gboolean updating; /**< Whether the control is synchronizing widgets. */ } ThemeFontControl; +/** + * @brief Theme CSS control binding. + */ +typedef struct { + ThemeDialogState *dialog; /**< Theme dialog state. */ + GtkWidget *entry; /**< CSS path entry. */ + gboolean updating; /**< Whether the control is synchronizing widgets. */ +} ThemeCssControl; + /** * @brief Duplicate a nullable string. * @details Application glue touches actions, tabs, panels, and persistent state. Keeping the contract explicit here makes UI callbacks easier to audit when a later change moves work between the window and child widgets. @@ -508,8 +521,11 @@ static ThemeState *theme_state_from_window(EditorWindow *win) { COPY_FIELD(sidebar_bg_color); COPY_FIELD(tabbar_bg_color); COPY_FIELD(tabbar_fg_color); + COPY_FIELD(tabbar_border_color); COPY_FIELD(tab_active_bg_color); COPY_FIELD(tab_active_fg_color); + COPY_FIELD(tab_active_border_color); + COPY_FIELD(tab_tiled_indicator_color); COPY_FIELD(topbar_bg_color); COPY_FIELD(topbar_fg_color); COPY_FIELD(bottombar_bg_color); @@ -576,6 +592,7 @@ static ThemeState *theme_state_from_window(EditorWindow *win) { COPY_FIELD(preview_font); COPY_FIELD(terminal_font); COPY_FIELD(code_font); + COPY_FIELD(theme_css_path); #undef COPY_FIELD return state; } @@ -602,8 +619,11 @@ static void theme_state_free(ThemeState *state) { FREE_FIELD(sidebar_bg_color); FREE_FIELD(tabbar_bg_color); FREE_FIELD(tabbar_fg_color); + FREE_FIELD(tabbar_border_color); FREE_FIELD(tab_active_bg_color); FREE_FIELD(tab_active_fg_color); + FREE_FIELD(tab_active_border_color); + FREE_FIELD(tab_tiled_indicator_color); FREE_FIELD(topbar_bg_color); FREE_FIELD(topbar_fg_color); FREE_FIELD(bottombar_bg_color); @@ -670,6 +690,7 @@ static void theme_state_free(ThemeState *state) { FREE_FIELD(preview_font); FREE_FIELD(terminal_font); FREE_FIELD(code_font); + FREE_FIELD(theme_css_path); #undef FREE_FIELD g_free(state); } @@ -699,8 +720,11 @@ static void theme_state_apply_to_window(ThemeState *state, EditorWindow *win) { APPLY_FIELD(sidebar_bg_color); APPLY_FIELD(tabbar_bg_color); APPLY_FIELD(tabbar_fg_color); + APPLY_FIELD(tabbar_border_color); APPLY_FIELD(tab_active_bg_color); APPLY_FIELD(tab_active_fg_color); + APPLY_FIELD(tab_active_border_color); + APPLY_FIELD(tab_tiled_indicator_color); APPLY_FIELD(topbar_bg_color); APPLY_FIELD(topbar_fg_color); APPLY_FIELD(bottombar_bg_color); @@ -767,14 +791,15 @@ static void theme_state_apply_to_window(ThemeState *state, EditorWindow *win) { APPLY_FIELD(preview_font); APPLY_FIELD(terminal_font); APPLY_FIELD(code_font); + APPLY_FIELD(theme_css_path); #undef APPLY_FIELD } /** - * @brief Load one color key from an INI theme into the scratch state. - * @details Theme files intentionally use the same `[Editor]` keys as - * `config.ini`. Invalid or absent colors are ignored so a custom theme - * can override only the roles it cares about. + * @brief Load one legacy INI color key into the scratch state. + * @details Legacy theme imports intentionally use the same `[Editor]` keys as + * old config-backed themes. Invalid or absent colors are ignored so + * an import can override only the roles it cares about. * @param key_file The parsed theme file. * @param key The config key to read. * @param slot The scratch theme color slot to replace. @@ -812,8 +837,11 @@ static void theme_state_load_key_file(ThemeState *state, GKeyFile *key_file) { LOAD_THEME_COLOR("sidebar_background_color", sidebar_bg_color); LOAD_THEME_COLOR("tabbar_background_color", tabbar_bg_color); LOAD_THEME_COLOR("tabbar_foreground_color", tabbar_fg_color); + LOAD_THEME_COLOR("tabbar_border_color", tabbar_border_color); LOAD_THEME_COLOR("tab_active_background_color", tab_active_bg_color); LOAD_THEME_COLOR("tab_active_foreground_color", tab_active_fg_color); + LOAD_THEME_COLOR("tab_active_border_color", tab_active_border_color); + LOAD_THEME_COLOR("tab_tiled_indicator_color", tab_tiled_indicator_color); LOAD_THEME_COLOR("topbar_background_color", topbar_bg_color); LOAD_THEME_COLOR("topbar_foreground_color", topbar_fg_color); LOAD_THEME_COLOR("bottombar_background_color", bottombar_bg_color); @@ -878,6 +906,165 @@ static void theme_state_load_key_file(ThemeState *state, GKeyFile *key_file) { #undef LOAD_THEME_COLOR } +/** + * @brief Load one managed CSS color variable into ThemeState. + * @details The Theme dialog only parses Graptoς-managed variables. Hand-written + * CSS remains in the file and is applied by GTK, but it is not guessed + * back into structured controls. + * @param css CSS file contents. + * @param name Variable name without the `graptos_` prefix. + * @param slot ThemeState color slot. + */ +static void theme_state_load_css_color(const char *css, + const char *name, + char **slot) { + if (!css || !name || !slot) return; + g_autofree char *needle = g_strdup_printf("@define-color graptos_%s", name); + char *pos = strstr(css, needle); + if (!pos) return; + pos += strlen(needle); + while (*pos == ' ' || *pos == '\t') pos++; + char *end = pos; + while (*end && *end != ';' && *end != '\n' && *end != '\r' && + *end != ' ' && *end != '\t') { + end++; + } + if (end <= pos) return; + g_autofree char *value = g_strndup(pos, (gsize)(end - pos)); + GdkRGBA rgba; + if (!value || !gdk_rgba_parse(&rgba, value)) return; + theme_replace(slot, value); +} + +/** + * @brief Load one managed CSS font metadata value into ThemeState. + * @details Font metadata is stored in comments because users may still write + * direct CSS font rules outside the managed block. + * @param css CSS file contents. + * @param name Metadata key. + * @param slot ThemeState font slot. + */ +static void theme_state_load_css_font(const char *css, + const char *name, + char **slot) { + if (!css || !name || !slot) return; + g_autofree char *needle = g_strdup_printf("/* @graptos-%s-font:", name); + char *pos = strstr(css, needle); + if (!pos) return; + pos += strlen(needle); + char *end = strstr(pos, "*/"); + if (!end) return; + g_autofree char *value = g_strndup(pos, (gsize)(end - pos)); + if (!value) return; + g_strstrip(value); + theme_replace(slot, value); +} + +/** + * @brief Apply managed CSS theme values to ThemeState. + * @details This is the CSS equivalent of legacy INI import. It intentionally + * mirrors the same complete field list so the structured Theme dialog + * remains the editor for CSS-backed themes. + * @param state The scratch theme state being edited. + * @param css CSS file contents. + */ +static void theme_state_load_css(ThemeState *state, const char *css) { + if (!state || !css) return; +#define LOAD_CSS_COLOR(name, field) \ + theme_state_load_css_color(css, name, &state->field) + LOAD_CSS_COLOR("editor_bg", editor_bg_color); + LOAD_CSS_COLOR("editor_fg", editor_fg_color); + LOAD_CSS_COLOR("editor_gutter_bg", editor_gutter_bg_color); + LOAD_CSS_COLOR("editor_gutter_fg", editor_gutter_fg_color); + LOAD_CSS_COLOR("editor_current_line_bg", editor_current_line_bg_color); + LOAD_CSS_COLOR("editor_selection_bg", editor_selection_bg_color); + LOAD_CSS_COLOR("editor_selection_fg", editor_selection_fg_color); + LOAD_CSS_COLOR("editor_cursor", editor_cursor_color); + LOAD_CSS_COLOR("sidebar_bg", sidebar_bg_color); + LOAD_CSS_COLOR("tabbar_bg", tabbar_bg_color); + LOAD_CSS_COLOR("tabbar_fg", tabbar_fg_color); + LOAD_CSS_COLOR("tabbar_border", tabbar_border_color); + LOAD_CSS_COLOR("tab_active_bg", tab_active_bg_color); + LOAD_CSS_COLOR("tab_active_fg", tab_active_fg_color); + LOAD_CSS_COLOR("tab_active_border", tab_active_border_color); + LOAD_CSS_COLOR("tab_tiled_indicator", tab_tiled_indicator_color); + LOAD_CSS_COLOR("topbar_bg", topbar_bg_color); + LOAD_CSS_COLOR("topbar_fg", topbar_fg_color); + LOAD_CSS_COLOR("bottombar_bg", bottombar_bg_color); + LOAD_CSS_COLOR("bottombar_fg", bottombar_fg_color); + LOAD_CSS_COLOR("status_error", status_error_color); + LOAD_CSS_COLOR("button_bg", button_bg_color); + LOAD_CSS_COLOR("button_fg", button_fg_color); + LOAD_CSS_COLOR("button_hover_bg", button_hover_bg_color); + LOAD_CSS_COLOR("button_active_bg", button_active_bg_color); + LOAD_CSS_COLOR("input_bg", input_bg_color); + LOAD_CSS_COLOR("input_fg", input_fg_color); + LOAD_CSS_COLOR("input_border", input_border_color); + LOAD_CSS_COLOR("project_tree_fg", project_tree_fg_color); + LOAD_CSS_COLOR("project_tree_selected_bg", project_tree_selected_bg_color); + LOAD_CSS_COLOR("project_tree_selected_fg", project_tree_selected_fg_color); + LOAD_CSS_COLOR("git_status_modified", git_status_modified_color); + LOAD_CSS_COLOR("git_status_added", git_status_added_color); + LOAD_CSS_COLOR("git_status_deleted", git_status_deleted_color); + LOAD_CSS_COLOR("git_status_renamed", git_status_renamed_color); + LOAD_CSS_COLOR("git_status_conflict", git_status_conflict_color); + LOAD_CSS_COLOR("git_status_untracked", git_status_untracked_color); + LOAD_CSS_COLOR("git_status_staged", git_status_staged_color); + LOAD_CSS_COLOR("scroll_preview_bg", scroll_preview_bg_color); + LOAD_CSS_COLOR("scroll_preview_fg", scroll_preview_fg_color); + LOAD_CSS_COLOR("popover_bg", popover_bg_color); + LOAD_CSS_COLOR("popover_border", popover_border_color); + LOAD_CSS_COLOR("tooltip_bg", tooltip_bg_color); + LOAD_CSS_COLOR("tooltip_fg", tooltip_fg_color); + LOAD_CSS_COLOR("tooltip_border", tooltip_border_color); + LOAD_CSS_COLOR("ref_popover_bg", ref_popover_bg_color); + LOAD_CSS_COLOR("ref_popover_fg", ref_popover_fg_color); + LOAD_CSS_COLOR("ref_popover_heading", ref_popover_heading_color); + LOAD_CSS_COLOR("ref_popover_title", ref_popover_title_color); + LOAD_CSS_COLOR("ref_popover_kind", ref_popover_kind_color); + LOAD_CSS_COLOR("ref_popover_snippet", ref_popover_snippet_color); + LOAD_CSS_COLOR("ref_popover_hover_bg", ref_popover_hover_bg_color); + LOAD_CSS_COLOR("ref_popover_hover_fg", ref_popover_hover_fg_color); + LOAD_CSS_COLOR("completion_popover_bg", completion_popover_bg_color); + LOAD_CSS_COLOR("completion_popover_fg", completion_popover_fg_color); + LOAD_CSS_COLOR("completion_popover_detail", completion_popover_detail_color); + LOAD_CSS_COLOR("completion_selection_bg", completion_selection_bg_color); + LOAD_CSS_COLOR("completion_selection_fg", completion_selection_fg_color); + if (!strstr(css, "graptos_tab_active_border")) { + theme_replace(&state->tab_active_border_color, + state->completion_selection_bg_color); + } + if (!strstr(css, "graptos_tab_tiled_indicator")) { + theme_replace(&state->tab_tiled_indicator_color, + state->completion_selection_bg_color); + } + LOAD_CSS_COLOR("dialog_bg", dialog_bg_color); + LOAD_CSS_COLOR("dialog_fg", dialog_fg_color); + LOAD_CSS_COLOR("dialog_border", dialog_border_color); + LOAD_CSS_COLOR("dialog_title", dialog_title_color); + LOAD_CSS_COLOR("dialog_body", dialog_body_color); + LOAD_CSS_COLOR("dialog_muted", dialog_muted_color); + LOAD_CSS_COLOR("dialog_output", dialog_output_color); + LOAD_CSS_COLOR("git_output_bg", git_output_bg_color); + LOAD_CSS_COLOR("dialog_action", dialog_action_color); + LOAD_CSS_COLOR("dialog_destructive_action", dialog_destructive_action_color); + LOAD_CSS_COLOR("dialog_input_fg", dialog_input_fg_color); + LOAD_CSS_COLOR("dialog_input_bg", dialog_input_bg_color); + LOAD_CSS_COLOR("search_match_bg", search_match_bg_color); + LOAD_CSS_COLOR("search_match_fg", search_match_fg_color); + LOAD_CSS_COLOR("diagnostic_warning_bg", diagnostic_warning_bg_color); + LOAD_CSS_COLOR("diagnostic_warning_fg", diagnostic_warning_fg_color); + LOAD_CSS_COLOR("codex_preview_bg", codex_preview_bg_color); + LOAD_CSS_COLOR("codex_preview_fg", codex_preview_fg_color); + LOAD_CSS_COLOR("codex_prompt_bg", codex_prompt_bg_color); +#undef LOAD_CSS_COLOR + theme_state_load_css_font(css, "ui", &state->ui_font); + theme_state_load_css_font(css, "editor", &state->editor_font); + theme_state_load_css_font(css, "preview", &state->preview_font); + theme_state_load_css_font(css, "terminal", &state->terminal_font); + theme_state_load_css_font(css, "code", &state->code_font); +} + /** * @brief Return a valid color or fallback. * @details Application glue touches actions, tabs, panels, and persistent state. Keeping the contract explicit here makes UI callbacks easier to audit when a later change moves work between the window and child widgets. @@ -941,14 +1128,27 @@ static void theme_preview_update(ThemeDialogState *dialog) { const char *tooltip_border = theme_color(s->tooltip_border_color, popover_border); g_string_append_printf(css, ".graptos-theme-preview-root { background: %s; color: %s; }\n" + ".graptos-theme-preview-section { font-weight: 700; margin-top: 8px; }\n" + ".graptos-theme-preview-surface { padding: 8px; }\n" + ".graptos-theme-preview-row { border-spacing: 6px; }\n" ".graptos-theme-preview-top { background: %s; color: %s; padding: 6px; }\n" + ".graptos-theme-preview-tabbar { background: %s; color: %s; padding: 6px; border: 1px solid %s; }\n" + ".graptos-theme-preview-tab-active { background: %s; color: %s; padding: 4px; border: 1px solid %s; border-bottom-color: %s; }\n" + ".graptos-theme-preview-tab-tiled { background: %s; color: %s; padding: 4px; border: 1px solid %s; box-shadow: inset 0 -2px %s; }\n" ".graptos-theme-preview-bottom { background: %s; color: %s; padding: 6px; }\n" + ".graptos-theme-preview-status-error { color: %s; font-weight: 800; }\n" ".graptos-theme-preview-editor { background: %s; color: %s; padding: 8px; }\n" ".graptos-theme-preview-gutter { background: %s; color: %s; padding: 8px; }\n" ".graptos-theme-preview-current { background: %s; padding: 2px; }\n" ".graptos-theme-preview-selection { background: %s; color: %s; padding: 2px; }\n" + ".graptos-theme-preview-cursor { background: %s; color: %s; padding: 1px 3px; }\n" ".graptos-theme-preview-project { background: %s; color: %s; padding: 8px; }\n" ".graptos-theme-preview-project-selected { background: %s; color: %s; padding: 2px; }\n" + ".graptos-theme-preview-button { background: %s; color: %s; padding: 4px 8px; }\n" + ".graptos-theme-preview-button-hover { background: %s; color: %s; padding: 4px 8px; }\n" + ".graptos-theme-preview-button-active { background: %s; color: %s; padding: 4px 8px; }\n" + ".graptos-theme-preview-input { background: %s; color: %s; padding: 4px 8px; border: 1px solid %s; }\n" + ".graptos-theme-preview-minimap { background: %s; color: %s; padding: 8px; }\n" ".graptos-theme-preview-popover { background: %s; color: %s; padding: 8px; border: 1px solid %s; }\n" ".graptos-theme-preview-tooltip { background: %s; color: %s; padding: 6px; border: 1px solid %s; }\n" ".graptos-theme-preview-completion { background: %s; color: %s; padding: 8px; }\n" @@ -970,17 +1170,37 @@ static void theme_preview_update(ThemeDialogState *dialog) { ".graptos-theme-preview-dialog-input { background: %s; color: %s; padding: 3px; }\n" ".graptos-theme-preview-codex { background: %s; color: %s; padding: 8px; }\n" ".graptos-theme-preview-codex-prompt { background: %s; padding: 4px; }\n" + ".graptos-theme-preview-codex-approval { background: %s; color: %s; border: 1px solid %s; padding: 4px; }\n" + ".graptos-theme-preview-codex-review { background: %s; color: %s; border: 1px solid %s; padding: 4px; }\n" ".graptos-theme-preview-search { background: %s; color: %s; padding: 2px; }\n" ".graptos-theme-preview-warning { background: %s; color: %s; padding: 2px; }\n", bg, fg, theme_color(s->topbar_bg_color, bg), theme_color(s->topbar_fg_color, fg), + theme_color(s->tabbar_bg_color, bg), theme_color(s->tabbar_fg_color, fg), + theme_color(s->tabbar_border_color, "#00000000"), + theme_color(s->tab_active_bg_color, "#2a2e3d"), theme_color(s->tab_active_fg_color, fg), + theme_color(s->tabbar_border_color, "#00000000"), + theme_color(s->tab_active_border_color, "#89b4fa"), + theme_color(s->tab_active_bg_color, "#2a2e3d"), theme_color(s->tab_active_fg_color, fg), + theme_color(s->tabbar_border_color, "#00000000"), + theme_color(s->tab_tiled_indicator_color, + theme_color(s->tab_active_border_color, "#89b4fa")), theme_color(s->bottombar_bg_color, bg), theme_color(s->bottombar_fg_color, fg), + theme_color(s->status_error_color, "#ff6b6b"), bg, fg, theme_color(s->editor_gutter_bg_color, bg), theme_color(s->editor_gutter_fg_color, "#8b949e"), theme_color(s->editor_current_line_bg_color, "#20232b"), theme_color(s->editor_selection_bg_color, "#3a405c"), theme_color(s->editor_selection_fg_color, "#ffffff"), + theme_color(s->editor_cursor_color, "#ffffff"), bg, theme_color(s->sidebar_bg_color, bg), theme_color(s->project_tree_fg_color, fg), theme_color(s->project_tree_selected_bg_color, "#2a2e3d"), theme_color(s->project_tree_selected_fg_color, "#ffffff"), + theme_color(s->button_bg_color, "#2a2e3d"), theme_color(s->button_fg_color, fg), + theme_color(s->button_hover_bg_color, "#343a4a"), theme_color(s->button_fg_color, fg), + theme_color(s->button_active_bg_color, "#3f4659"), theme_color(s->button_fg_color, fg), + theme_color(s->input_bg_color, bg), theme_color(s->input_fg_color, fg), + theme_color(s->input_border_color, "#3f4659"), + theme_color(s->scroll_preview_bg_color, "#20232b"), + theme_color(s->scroll_preview_fg_color, "#8b949e"), popover, fg, popover_border, tooltip, tooltip_fg, tooltip_border, theme_color(s->completion_popover_bg_color, popover), theme_color(s->completion_popover_fg_color, fg), @@ -1004,6 +1224,12 @@ static void theme_preview_update(ThemeDialogState *dialog) { theme_color(s->dialog_input_fg_color, fg), theme_color(s->codex_preview_bg_color, bg), theme_color(s->codex_preview_fg_color, fg), theme_color(s->codex_prompt_bg_color, bg), + theme_color(s->diagnostic_warning_bg_color, "#5f4b24"), + theme_color(s->diagnostic_warning_fg_color, "#ffd166"), + theme_color(s->diagnostic_warning_fg_color, "#ffd166"), + theme_color(s->search_match_bg_color, "#515c7a"), + theme_color(s->search_match_fg_color, "#ffffff"), + theme_color(s->search_match_fg_color, "#ffffff"), theme_color(s->search_match_bg_color, "#515c7a"), theme_color(s->search_match_fg_color, "#ffffff"), theme_color(s->diagnostic_warning_bg_color, "#5f4b24"), theme_color(s->diagnostic_warning_fg_color, "#ffd166")); g_string_append_printf(css, @@ -1046,6 +1272,91 @@ static void theme_dialog_changed(ThemeDialogState *dialog) { theme_preview_update(dialog); } +/** + * @brief Return the default editable CSS path for theme controls. + * @details The theme editor uses the same per-user path as first-run config so + * a user can re-enable CSS overrides without remembering the directory. + * @return An owned path, or NULL when the config directory cannot be resolved. + */ +static char *theme_default_css_path(void) { + const char *base = g_get_user_config_dir(); + if (!base || base[0] == '\0') return NULL; + return g_build_filename(base, "graptos", "theme.css", NULL); +} + +/** + * @brief Synchronize the theme CSS controls from ThemeState. + * @details Presets and file loads can change CSS settings without user typing, + * so controls need the same explicit refresh path as colors and fonts. + * @param control The control supplied by the caller. + */ +static void theme_css_control_sync(ThemeCssControl *control) { + if (!control || !control->dialog || !control->dialog->state) return; + ThemeState *state = control->dialog->state; + control->updating = TRUE; + if (control->entry) { + gtk_editable_set_text(GTK_EDITABLE(control->entry), + state->theme_css_path ? state->theme_css_path : ""); + } + control->updating = FALSE; +} + +/** + * @brief Store the theme CSS path from the entry. + * @details Empty text is preserved as an explicit disabled path. That gives + * manual config edits and the theme dialog the same opt-out behavior. + * @param editable The editable supplied by the caller. + * @param user_data The callback context passed through GTK signal data. + */ +static void theme_css_path_changed(GtkEditable *editable, gpointer user_data) { + ThemeCssControl *control = user_data; + if (!control || control->updating || !control->dialog || + !control->dialog->state || !editable) { + return; + } + const char *text = gtk_editable_get_text(editable); + if (g_strcmp0(control->dialog->state->theme_css_path, text ? text : "") == 0) { + return; + } + theme_replace(&control->dialog->state->theme_css_path, text ? text : ""); + theme_dialog_changed(control->dialog); +} + +/** + * @brief Create or refresh the editable CSS template file. + * @details The button writes only the selected CSS path. It also fills the + * default per-user path when the entry is blank so the next Apply can + * persist a usable CSS theme file. + * @param widget The widget that emitted the callback. + * @param user_data The callback context passed through GTK signal data. + */ +static void theme_css_template_clicked(GtkWidget *widget, gpointer user_data) { + (void)widget; + ThemeCssControl *control = user_data; + if (!control || !control->dialog || !control->dialog->state) return; + + ThemeState *state = control->dialog->state; + if (!state->theme_css_path || state->theme_css_path[0] == '\0') { + g_autofree char *path = theme_default_css_path(); + if (path) theme_replace(&state->theme_css_path, path); + } + if (!state->theme_css_path || state->theme_css_path[0] == '\0') { + app_window_set_error_status(control->dialog->win, + "CSS template failed", + "Unable to resolve config directory."); + return; + } + if (!graptos_write_theme_css_template(state->theme_css_path, FALSE)) { + app_window_set_error_status(control->dialog->win, + "CSS template failed", + state->theme_css_path); + return; + } + theme_css_control_sync(control); + theme_dialog_changed(control->dialog); + app_window_set_status(control->dialog->win, "CSS theme file ready"); +} + /** * @brief Convert RGBA to config color text. * @details Application glue touches actions, tabs, panels, and persistent state. Keeping the contract explicit here makes UI callbacks easier to audit when a later change moves work between the window and child widgets. @@ -1434,6 +1745,9 @@ static void theme_controls_sync_recursive(GtkWidget *widget) { ThemeFontControl *font_control = g_object_get_data(G_OBJECT(widget), "graptos-theme-font-control"); if (font_control) theme_font_control_sync(font_control); + ThemeCssControl *css_control = + g_object_get_data(G_OBJECT(widget), "graptos-theme-css-control"); + if (css_control) theme_css_control_sync(css_control); for (GtkWidget *child = gtk_widget_get_first_child(widget); child; @@ -1494,11 +1808,12 @@ static char *theme_preset_path(const ThemePreset *preset) { } /** - * @brief Load a theme INI file into the dialog scratch state. - * @details Loading changes only the dialog preview state. The real application - * config is updated later when the user presses Apply. + * @brief Load a theme file into the dialog scratch state. + * @details INI files update structured color/font controls. CSS files update + * the post-generated override path so themes can be edited after + * compilation without replacing the existing INI workflow. * @param dialog The theme dialog state. - * @param path The theme INI path. + * @param path The theme path. * @param label Name used in status/error messages. * @return TRUE when the theme loaded. */ @@ -1506,6 +1821,32 @@ static gboolean theme_dialog_load_file(ThemeDialogState *dialog, const char *path, const char *label) { if (!dialog || !dialog->state || !path) return FALSE; + g_autofree char *lower_path = g_ascii_strdown(path, -1); + if (lower_path && g_str_has_suffix(lower_path, ".css")) { + if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) { + app_window_set_error_status(dialog->win, + "Theme load failed", + path); + return FALSE; + } + g_autofree char *css = NULL; + gsize length = 0u; + g_autoptr(GError) read_error = NULL; + if (!g_file_get_contents(path, &css, &length, &read_error) || !css) { + app_window_set_error_status(dialog->win, + "Theme load failed", + read_error ? read_error->message : path); + return FALSE; + } + theme_replace(&dialog->state->theme_css_path, path); + theme_state_load_css(dialog->state, css); + theme_controls_sync(dialog); + dialog->dirty = TRUE; + theme_preview_update(dialog); + app_window_set_status(dialog->win, label ? label : "CSS theme loaded"); + return TRUE; + } + g_autoptr(GKeyFile) key_file = g_key_file_new(); g_autoptr(GError) error = NULL; if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, &error)) { @@ -1558,13 +1899,18 @@ static void theme_preset_changed(GtkDropDown *drop_down, } g_autofree char *message = g_strdup_printf("Loaded theme: %s", preset->label); - (void)theme_dialog_load_file(dialog, path, message); + g_autofree char *active_path = + g_strdup(dialog->state->theme_css_path ? dialog->state->theme_css_path : ""); + if (theme_dialog_load_file(dialog, path, message)) { + theme_replace(&dialog->state->theme_css_path, active_path); + theme_controls_sync(dialog); + } } /** - * @brief Load a custom Graptoς theme INI file. - * @details Custom themes use the same `[Editor]` keys as `config.ini`, making - * them easy to create by copying a saved config or bundled preset. + * @brief Load a custom Graptoς theme file. + * @details CSS is the primary theme format. Legacy INI files are still accepted + * here as imports and are written back into the active CSS theme. * @param widget The button that triggered the load. * @param user_data The ThemeDialogState. */ @@ -1650,13 +1996,53 @@ static GtkWidget *theme_preset_row(ThemeDialogState *dialog) { gtk_box_append(GTK_BOX(row), drop_down); gtk_box_append(GTK_BOX(row), - graptos_flat_button_new("Load .ini", - "Load a custom Graptoς theme file", + graptos_flat_button_new("Load Theme", + "Load a custom Graptoς .ini or .css theme file", G_CALLBACK(theme_load_file_clicked), dialog)); return row; } +/** + * @brief Create the theme CSS controls for the theme dialog. + * @details This row exposes the active CSS theme path directly. The file is + * the persisted theme source; structured controls rewrite its managed + * block when the user applies changes. + * @param dialog The theme dialog state. + * @return A GTK row containing CSS path controls. + */ +static GtkWidget *theme_css_row(ThemeDialogState *dialog) { + GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + GtkWidget *label = gtk_label_new("CSS file"); + gtk_label_set_xalign(GTK_LABEL(label), 0.0f); + gtk_widget_set_hexpand(label, TRUE); + gtk_box_append(GTK_BOX(row), label); + + ThemeCssControl *control = g_new0(ThemeCssControl, 1); + control->dialog = dialog; + + GtkWidget *entry = gtk_entry_new(); + gtk_widget_set_size_request(entry, 260, -1); + gtk_widget_set_hexpand(entry, TRUE); + gtk_widget_set_tooltip_text(entry, + "Path to an editable Graptoς CSS theme file"); + control->entry = entry; + + g_object_set_data_full(G_OBJECT(row), "graptos-theme-css-control", + control, g_free); + g_signal_connect(entry, "changed", + G_CALLBACK(theme_css_path_changed), control); + + theme_css_control_sync(control); + gtk_box_append(GTK_BOX(row), entry); + gtk_box_append(GTK_BOX(row), + graptos_flat_button_new("Create", + "Create the editable CSS theme file", + G_CALLBACK(theme_css_template_clicked), + control)); + return row; +} + /** * @brief Create a row containing a color selector. * @details Application glue touches actions, tabs, panels, and persistent state. Keeping the contract explicit here makes UI callbacks easier to audit when a later change moves work between the window and child widgets. @@ -1802,6 +2188,18 @@ static void theme_preview_label(GtkWidget *box, gtk_box_append(GTK_BOX(box), label); } +/** + * @brief Append a labeled preview section. + * @details The preview is also a theme audit surface. Section labels make it + * obvious which family owns each sample without adding separate theme + * state or hidden mappings. + * @param box The box supplied by the caller. + * @param text The text fragment supplied by the caller. + */ +static void theme_preview_section(GtkWidget *box, const char *text) { + theme_preview_label(box, text, "graptos-theme-preview-section"); +} + /** * @brief Build the rendered theme preview. * @details Application glue touches actions, tabs, panels, and persistent state. Keeping the contract explicit here makes UI callbacks easier to audit when a later change moves work between the window and child widgets. @@ -1812,9 +2210,15 @@ static GtkWidget *theme_preview_new(void) { gtk_widget_add_css_class(box, "graptos-theme-preview-root"); graptos_set_all_margins(box, 10); + theme_preview_section(box, "Chrome"); theme_preview_label(box, "Top bar New Open Folder", "graptos-theme-preview-top"); - theme_preview_label(box, "Tab: main.c Tab: README.md", "graptos-theme-preview-bottom"); + theme_preview_label(box, "Tab bar foreground main.c README.md", "graptos-theme-preview-tabbar"); + theme_preview_label(box, "Active tab", "graptos-theme-preview-tab-active"); + theme_preview_label(box, "Tiled tab indicator", "graptos-theme-preview-tab-tiled"); + theme_preview_label(box, "Bottom/status bar Ready", "graptos-theme-preview-bottom"); + theme_preview_label(box, "Status error text", "graptos-theme-preview-status-error"); + theme_preview_section(box, "Editor"); GtkWidget *editor = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); GtkWidget *gutter = gtk_label_new(" 1\n 2\n 3"); gtk_widget_add_css_class(gutter, "graptos-theme-preview-gutter"); @@ -1824,10 +2228,13 @@ static GtkWidget *theme_preview_new(void) { theme_preview_label(code, "int main(void) {", "graptos-theme-preview-code"); theme_preview_label(code, " return 0; ← current line", "graptos-theme-preview-current"); theme_preview_label(code, "selected text", "graptos-theme-preview-selection"); + theme_preview_label(code, "cursor sample |", "graptos-theme-preview-cursor"); gtk_box_append(GTK_BOX(editor), code); gtk_box_append(GTK_BOX(box), editor); + theme_preview_section(box, "Project Tree and Git"); theme_preview_label(box, "Project Tree\nM src/app.c\n? notes.md", "graptos-theme-preview-project"); + theme_preview_label(box, "Selected project row", "graptos-theme-preview-project-selected"); theme_preview_label(box, "M modified", "graptos-theme-preview-git-modified"); theme_preview_label(box, "A added", "graptos-theme-preview-git-added"); theme_preview_label(box, "D deleted", "graptos-theme-preview-git-deleted"); @@ -1835,17 +2242,30 @@ static GtkWidget *theme_preview_new(void) { theme_preview_label(box, "U conflict", "graptos-theme-preview-git-conflict"); theme_preview_label(box, "? untracked", "graptos-theme-preview-git-untracked"); theme_preview_label(box, "S staged", "graptos-theme-preview-git-staged"); - theme_preview_label(box, "Selected project row", "graptos-theme-preview-project-selected"); + + theme_preview_section(box, "Controls and Preview"); + theme_preview_label(box, "Button", "graptos-theme-preview-button"); + theme_preview_label(box, "Button hover", "graptos-theme-preview-button-hover"); + theme_preview_label(box, "Button active", "graptos-theme-preview-button-active"); + theme_preview_label(box, "Input field text", "graptos-theme-preview-input"); + theme_preview_label(box, "Scroll preview / minimap", "graptos-theme-preview-minimap"); + + theme_preview_section(box, "Popovers"); theme_preview_label(box, "Popover demo\nDummy popover text", "graptos-theme-preview-popover"); theme_preview_label(box, "Tooltip demo", "graptos-theme-preview-tooltip"); theme_preview_label(box, "Completion\nprintf", "graptos-theme-preview-completion"); theme_preview_label(box, "detail: stdio function", "graptos-theme-preview-completion-detail"); theme_preview_label(box, "Selected completion row", "graptos-theme-preview-completion-selected"); - theme_preview_label(box, "Reference Popover", "graptos-theme-preview-ref-heading"); + + theme_preview_section(box, "Reference Popover"); + theme_preview_label(box, "Reference popover surface", "graptos-theme-preview-ref"); + theme_preview_label(box, "Reference heading", "graptos-theme-preview-ref-heading"); theme_preview_label(box, "symbol_name", "graptos-theme-preview-ref-title"); theme_preview_label(box, "function", "graptos-theme-preview-ref-kind"); theme_preview_label(box, "return symbol_name();", "graptos-theme-preview-ref-snippet"); theme_preview_label(box, "Hovered reference row", "graptos-theme-preview-ref-hover"); + + theme_preview_section(box, "Dialogs"); theme_preview_label(box, "Native dialog preview\nSave changes?", "graptos-theme-preview-dialog"); theme_preview_label(box, "Dialog title", "graptos-theme-preview-dialog-title"); theme_preview_label(box, "Dialog body text explains the choice.", "graptos-theme-preview-dialog-body"); @@ -1854,8 +2274,12 @@ static GtkWidget *theme_preview_new(void) { theme_preview_label(box, "Save action", "graptos-theme-preview-dialog-action"); theme_preview_label(box, "Discard action", "graptos-theme-preview-dialog-destructive"); theme_preview_label(box, "Dialog input text", "graptos-theme-preview-dialog-input"); + + theme_preview_section(box, "Search, Diagnostics, Codex, Fonts"); theme_preview_label(box, "Codex response preview", "graptos-theme-preview-codex"); theme_preview_label(box, "Ask Codex…", "graptos-theme-preview-codex-prompt"); + theme_preview_label(box, "Codex approval panel", "graptos-theme-preview-codex-approval"); + theme_preview_label(box, "Codex review panel", "graptos-theme-preview-codex-review"); theme_preview_label(box, "Search match", "graptos-theme-preview-search"); theme_preview_label(box, "Diagnostic warning", "graptos-theme-preview-warning"); theme_preview_label(box, "$ make check", "graptos-theme-preview-terminal"); @@ -1877,6 +2301,7 @@ static GtkWidget *theme_controls_new(ThemeDialogState *dialog) { gtk_box_append(GTK_BOX(box), theme_section_label("Themes")); gtk_box_append(GTK_BOX(box), theme_preset_row(dialog)); + gtk_box_append(GTK_BOX(box), theme_css_row(dialog)); gtk_box_append(GTK_BOX(box), theme_section_label("Fonts")); gtk_box_append(GTK_BOX(box), theme_font_row(dialog, "UI", &s->ui_font)); @@ -1909,8 +2334,11 @@ static GtkWidget *theme_controls_new(ThemeDialogState *dialog) { gtk_box_append(GTK_BOX(box), theme_section_label("Tabs and Project Tree")); theme_append_color(box, dialog, "Tabbar background", &s->tabbar_bg_color); theme_append_color(box, dialog, "Tabbar foreground", &s->tabbar_fg_color); + theme_append_color(box, dialog, "Tabbar border", &s->tabbar_border_color); theme_append_color(box, dialog, "Active tab background", &s->tab_active_bg_color); theme_append_color(box, dialog, "Active tab foreground", &s->tab_active_fg_color); + theme_append_color(box, dialog, "Active tab underline", &s->tab_active_border_color); + theme_append_color(box, dialog, "Tiled tab indicator", &s->tab_tiled_indicator_color); theme_append_color(box, dialog, "Project background", &s->sidebar_bg_color); theme_append_color(box, dialog, "Project foreground", &s->project_tree_fg_color); theme_append_color(box, dialog, "Project selected background", &s->project_tree_selected_bg_color); @@ -1997,7 +2425,7 @@ static int theme_unsaved_prompt(GtkWindow *parent) { parent, "Unsaved theme changes", "Save theme changes before closing?", - "You can save the live preview to config.ini, discard it, or keep editing.", + "You can save the live preview to the active CSS theme, discard it, or keep editing.", 460, 180, actions, @@ -2065,12 +2493,12 @@ void action_show_preferences(GtkWidget *widget, gpointer user_data) { G_CALLBACK(graptos_modal_window_respond), GINT_TO_POINTER(GRAPTOS_THEME_RESPONSE_CANCEL))); gtk_box_append(GTK_BOX(buttons), - graptos_flat_button_new("Reload config.ini", - "Apply the manually edited config.ini file", + graptos_flat_button_new("Reload Theme", + "Reload the saved config and active CSS theme", G_CALLBACK(graptos_modal_window_respond), GINT_TO_POINTER(GRAPTOS_THEME_RESPONSE_RELOAD_CONFIG))); gtk_box_append(GTK_BOX(buttons), - graptos_flat_button_new("Apply", "Apply theme and save config", + graptos_flat_button_new("Apply", "Apply theme and save CSS", G_CALLBACK(graptos_modal_window_respond), GINT_TO_POINTER(GRAPTOS_THEME_RESPONSE_APPLY))); gtk_box_append(GTK_BOX(root), buttons); @@ -2084,6 +2512,11 @@ void action_show_preferences(GtkWidget *widget, gpointer user_data) { GRAPTOS_THEME_RESPONSE_CANCEL); if (response == GRAPTOS_THEME_RESPONSE_APPLY) { theme_state_apply_to_window(dialog_state.state, win); + if (win->theme_css_path && win->theme_css_path[0] != '\0') { + (void)graptos_theme_css_save_from_window(win, + win->theme_css_path, + TRUE); + } app_window_apply_css(win); apply_preferences_to_all_tabs(win); graptos_config_save(win); @@ -2107,6 +2540,11 @@ void action_show_preferences(GtkWidget *widget, gpointer user_data) { int unsaved = theme_unsaved_prompt(GTK_WINDOW(dialog)); if (unsaved == GRAPTOS_THEME_RESPONSE_APPLY) { theme_state_apply_to_window(dialog_state.state, win); + if (win->theme_css_path && win->theme_css_path[0] != '\0') { + (void)graptos_theme_css_save_from_window(win, + win->theme_css_path, + TRUE); + } app_window_apply_css(win); apply_preferences_to_all_tabs(win); graptos_config_save(win); diff --git a/src/app/app_preference_actions.inc.c b/src/app/app_preference_actions.inc.c index ceb48c1..f6d2863 100644 --- a/src/app/app_preference_actions.inc.c +++ b/src/app/app_preference_actions.inc.c @@ -284,8 +284,11 @@ void action_reset_all_backgrounds(GtkWidget *widget, gpointer user_data) { g_clear_pointer(&win->sidebar_bg_color, g_free); g_clear_pointer(&win->tabbar_bg_color, g_free); g_clear_pointer(&win->tabbar_fg_color, g_free); + g_clear_pointer(&win->tabbar_border_color, g_free); g_clear_pointer(&win->tab_active_bg_color, g_free); g_clear_pointer(&win->tab_active_fg_color, g_free); + g_clear_pointer(&win->tab_active_border_color, g_free); + g_clear_pointer(&win->tab_tiled_indicator_color, g_free); g_clear_pointer(&win->topbar_bg_color, g_free); g_clear_pointer(&win->topbar_fg_color, g_free); g_clear_pointer(&win->bottombar_bg_color, g_free); diff --git a/src/app/app_window_lifecycle.inc.c b/src/app/app_window_lifecycle.inc.c index b52f7e3..5407292 100644 --- a/src/app/app_window_lifecycle.inc.c +++ b/src/app/app_window_lifecycle.inc.c @@ -103,6 +103,12 @@ EditorWindow *app_window_new(GtkApplication *application) { win->preview_font = g_strdup(""); win->terminal_font = g_strdup(""); win->code_font = g_strdup("monospace"); + { + const char *config_dir = g_get_user_config_dir(); + win->theme_css_path = config_dir && config_dir[0] != '\0' + ? g_build_filename(config_dir, "graptos", "theme.css", NULL) + : NULL; + } // Without defaults here, GTK paints white until the first file/tab refresh reapplies config CSS win->editor_bg_color = g_strdup("#181a1f"); win->editor_fg_color = g_strdup("#d4d4d4"); @@ -115,8 +121,11 @@ EditorWindow *app_window_new(GtkApplication *application) { win->sidebar_bg_color = g_strdup("#181a1f"); win->tabbar_bg_color = g_strdup("#181a1f"); win->tabbar_fg_color = g_strdup("#d4d4d4"); + win->tabbar_border_color = g_strdup("#00000000"); win->tab_active_bg_color = g_strdup("#20232b"); win->tab_active_fg_color = g_strdup("#ffffff"); + win->tab_active_border_color = g_strdup("#89b4fa"); + win->tab_tiled_indicator_color = g_strdup("#89b4fa"); win->topbar_bg_color = g_strdup("#181a1f"); win->topbar_fg_color = g_strdup("#d4d4d4"); win->bottombar_bg_color = g_strdup("#181a1f"); @@ -192,13 +201,13 @@ EditorWindow *app_window_new(GtkApplication *application) { graptos_config_load(win); win->lsp_client = lsp_client_new(win); if (!win->codex_preview_bg_color) { - win->codex_preview_bg_color = g_strdup(win->editor_bg_color); + win->codex_preview_bg_color = g_strdup("#1b1f24"); } if (!win->codex_preview_fg_color) { win->codex_preview_fg_color = g_strdup("#d4d4d4"); } if (!win->codex_prompt_bg_color) { - win->codex_prompt_bg_color = g_strdup(win->editor_bg_color); + win->codex_prompt_bg_color = g_strdup("#111318"); } /* @@ -345,8 +354,11 @@ void app_window_free(EditorWindow *win) { g_free(win->sidebar_bg_color); g_free(win->tabbar_bg_color); g_free(win->tabbar_fg_color); + g_free(win->tabbar_border_color); g_free(win->tab_active_bg_color); g_free(win->tab_active_fg_color); + g_free(win->tab_active_border_color); + g_free(win->tab_tiled_indicator_color); g_free(win->topbar_bg_color); g_free(win->topbar_fg_color); g_free(win->bottombar_bg_color); @@ -413,6 +425,7 @@ void app_window_free(EditorWindow *win) { g_free(win->preview_font); g_free(win->terminal_font); g_free(win->code_font); + g_free(win->theme_css_path); g_free(win->project_root); g_free(win); } diff --git a/src/app_state.c b/src/app_state.c index 13b7fc6..8c9fc61 100644 --- a/src/app_state.c +++ b/src/app_state.c @@ -27,8 +27,11 @@ void app_window_apply_css(EditorWindow *win) { win->editor_cursor_color, win->sidebar_bg_color, win->tabbar_bg_color, win->tabbar_fg_color, + win->tabbar_border_color, win->tab_active_bg_color, win->tab_active_fg_color, + win->tab_active_border_color, + win->tab_tiled_indicator_color, win->topbar_bg_color, win->topbar_fg_color, win->bottombar_bg_color, win->bottombar_fg_color, win->status_error_color, @@ -90,6 +93,7 @@ void app_window_apply_css(EditorWindow *win) { win->terminal_font, win->code_font, win->use_system_interface_font); + graptos_apply_theme_css(win->theme_css_path, TRUE); terminal_panel_apply_colors(win->terminal_panel); } diff --git a/src/codex_markdown.c b/src/codex_markdown.c index 6a73e5e..2abbe47 100644 --- a/src/codex_markdown.c +++ b/src/codex_markdown.c @@ -31,14 +31,20 @@ typedef struct { GtkTextBuffer *buffer; GtkTextIter iter; } Writer; static void tag_ensure(GtkTextBuffer *buffer, const char *name, const char *first, ...) { GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer); - if (gtk_text_tag_table_lookup(table, name)) return; - GtkTextTag *tag = gtk_text_tag_new(name); + GtkTextTag *tag = gtk_text_tag_table_lookup(table, name); + gboolean created = FALSE; + if (!tag) { + tag = gtk_text_tag_new(name); + created = TRUE; + } va_list args; va_start(args, first); g_object_set_valist(G_OBJECT(tag), first, args); va_end(args); - gtk_text_tag_table_add(table, tag); - g_object_unref(tag); + if (created) { + gtk_text_tag_table_add(table, tag); + g_object_unref(tag); + } } /** @@ -48,20 +54,38 @@ static void tag_ensure(GtkTextBuffer *buffer, const char *name, * response pane into a browser widget. * @param buffer The text buffer used for the operation. */ -static void tags_ensure(GtkTextBuffer *buffer) { +static void tags_ensure(GtkTextBuffer *buffer, + const char *heading_color, + const char *success_color, + const char *warning_color, + const char *code_fg_color, + const char *code_bg_color, + const char *quote_color) { + const char *heading = heading_color && heading_color[0] == '#' + ? heading_color : NULL; + const char *success = success_color && success_color[0] == '#' + ? success_color : heading; + const char *warning = warning_color && warning_color[0] == '#' + ? warning_color : heading; + const char *code_fg = code_fg_color && code_fg_color[0] == '#' + ? code_fg_color : NULL; + const char *code_bg = code_bg_color && code_bg_color[0] == '#' + ? code_bg_color : NULL; + const char *quote = quote_color && quote_color[0] == '#' + ? quote_color : code_fg; tag_ensure(buffer, "ai-h1", "weight", PANGO_WEIGHT_BOLD, "scale", 1.55, - "foreground", "#89b4fa", "pixels-above-lines", 10, NULL); + "foreground", heading, "pixels-above-lines", 10, NULL); tag_ensure(buffer, "ai-h2", "weight", PANGO_WEIGHT_BOLD, "scale", 1.30, - "foreground", "#a6e3a1", "pixels-above-lines", 8, NULL); + "foreground", success, "pixels-above-lines", 8, NULL); tag_ensure(buffer, "ai-h3", "weight", PANGO_WEIGHT_BOLD, "scale", 1.12, - "foreground", "#f9e2af", NULL); + "foreground", warning, NULL); tag_ensure(buffer, "ai-bold", "weight", PANGO_WEIGHT_BOLD, NULL); tag_ensure(buffer, "ai-italic", "style", PANGO_STYLE_ITALIC, NULL); - tag_ensure(buffer, "ai-code", "family", "monospace", "foreground", "#f1f1f1", - "background", "#282c34", NULL); + tag_ensure(buffer, "ai-code", "family", "monospace", "foreground", code_fg, + "background", code_bg, NULL); tag_ensure(buffer, "ai-quote", "style", PANGO_STYLE_ITALIC, - "foreground", "#b7c5d3", "left-margin", 12, NULL); - tag_ensure(buffer, "ai-link", "foreground", "#7dcfff", + "foreground", quote, "left-margin", 12, NULL); + tag_ensure(buffer, "ai-link", "foreground", heading, "underline", PANGO_UNDERLINE_SINGLE, NULL); } @@ -159,11 +183,30 @@ static void line_render(Writer *writer, const char *line, gboolean *code) { * current markdown string into styled text. * @param buffer The text buffer used for the operation. * @param markdown The markdown supplied by the caller. + * @param heading_color The primary heading/link color. + * @param success_color The secondary heading color. + * @param warning_color The tertiary heading color. + * @param code_fg_color The code foreground color. + * @param code_bg_color The code background color. + * @param quote_color The quoted text color. */ -void codex_markdown_render(GtkTextBuffer *buffer, const char *markdown) { +void codex_markdown_render(GtkTextBuffer *buffer, + const char *markdown, + const char *heading_color, + const char *success_color, + const char *warning_color, + const char *code_fg_color, + const char *code_bg_color, + const char *quote_color) { if (!buffer) return; gtk_text_buffer_set_text(buffer, "", 0); - tags_ensure(buffer); + tags_ensure(buffer, + heading_color, + success_color, + warning_color, + code_fg_color, + code_bg_color, + quote_color); Writer writer = { .buffer = buffer }; gtk_text_buffer_get_end_iter(buffer, &writer.iter); gboolean code = FALSE; diff --git a/src/codex_markdown.h b/src/codex_markdown.h index 4208d98..a449945 100644 --- a/src/codex_markdown.h +++ b/src/codex_markdown.h @@ -15,6 +15,19 @@ * @details Replaces the target buffer with styled text for the supported subset. * @param buffer The text buffer used for the operation. * @param markdown The markdown supplied by the caller. + * @param heading_color The primary heading/link color. + * @param success_color The secondary heading color. + * @param warning_color The tertiary heading color. + * @param code_fg_color The code foreground color. + * @param code_bg_color The code background color. + * @param quote_color The quoted text color. */ -void codex_markdown_render(GtkTextBuffer *buffer, const char *markdown); +void codex_markdown_render(GtkTextBuffer *buffer, + const char *markdown, + const char *heading_color, + const char *success_color, + const char *warning_color, + const char *code_fg_color, + const char *code_bg_color, + const char *quote_color); #endif diff --git a/src/codex_panel.c b/src/codex_panel.c index f8423a6..857757e 100644 --- a/src/codex_panel.c +++ b/src/codex_panel.c @@ -82,7 +82,14 @@ static void panel_render_now(CodexPanel *panel) { if (!panel || !panel->current_chat || !panel->current_chat->render_dirty) return; CodexChat *chat = panel->current_chat; - codex_markdown_render(chat->buffer, chat->markdown->str); + codex_markdown_render(chat->buffer, + chat->markdown->str, + panel->win ? panel->win->completion_selection_bg_color : NULL, + panel->win ? panel->win->git_status_added_color : NULL, + panel->win ? panel->win->git_status_modified_color : NULL, + panel->win ? panel->win->codex_preview_fg_color : NULL, + panel->win ? panel->win->codex_prompt_bg_color : NULL, + panel->win ? panel->win->dialog_muted_color : NULL); chat->render_dirty = FALSE; if (gtk_widget_get_mapped(chat->view) && gtk_widget_get_width(chat->view) > 0 && diff --git a/src/config.c b/src/config.c index deb8393..5292146 100644 --- a/src/config.c +++ b/src/config.c @@ -67,18 +67,6 @@ static void load_color(GKeyFile *key_file, const char *key, char **slot) { g_free(value); } -/** - * @brief Save color. - * @details Configuration values are user data, not internal constants. The comment makes the fallback path explicit so missing keys do not overwrite intentional manual edits. - * @param key_file The key file supplied by the caller. - * @param key The key supplied by the caller. - * @param value The value being parsed, stored, or applied. - */ -static void save_color(GKeyFile *key_file, const char *key, const char *value) { - if (!key_file || !key || !value || value[0] == '\0') return; - g_key_file_set_string(key_file, "Editor", key, value); -} - /** * @brief Load a string config value. * @details Configuration values are user data, not internal constants. The comment makes the fallback path explicit so missing keys do not overwrite intentional manual edits. @@ -184,6 +172,548 @@ static gboolean write_config_key_file(EditorWindow *win, return TRUE; } +/** + * @brief Return the default CSS theme path. + * @details CSS themes live next to config.ini because they are user-editable + * runtime state, not compiled resources. + * @return An owned path, or NULL when the config directory cannot be resolved. + */ +static char *theme_css_default_path(void) { + const char *base = g_get_user_config_dir(); + if (!base || base[0] == '\0') return NULL; + return g_build_filename(base, "graptos", "theme.css", NULL); +} + +/** + * @brief Replace a nullable string slot. + * @details Theme migration writes through this helper so config and CSS parsing + * use the same ownership rules for window strings. + * @param slot The owned string slot. + * @param value The new string value. + */ +static void replace_config_string(char **slot, const char *value) { + if (!slot) return; + g_free(*slot); + *slot = g_strdup(value ? value : ""); +} + +/** + * @brief Append one managed color variable. + * @details The managed block is intentionally explicit. Each variable maps to + * one Theme dialog control and one legacy config key during migration. + * @param css The CSS buffer being written. + * @param name The CSS variable name without the `graptos_` prefix. + * @param value The configured value. + * @param fallback The fallback value. + */ +static void append_theme_define(GString *css, + const char *name, + const char *value, + const char *fallback) { + if (!css || !name) return; + const char *resolved = value && value[0] == '#' ? value : fallback; + g_string_append_printf(css, + "@define-color graptos_%s %s;\n", + name, + resolved ? resolved : "#000000"); +} + +/** + * @brief Append one managed font metadata line. + * @details GTK CSS cannot be reliably round-tripped for every font selector, so + * the Theme dialog stores structured font values in comments beside + * the generated rules. + * @param css The CSS buffer being written. + * @param name The metadata key. + * @param value The font description. + */ +static void append_theme_font_meta(GString *css, + const char *name, + const char *value) { + if (!css || !name) return; + g_string_append_printf(css, + "/* @graptos-%s-font: %s */\n", + name, + value ? value : ""); +} + +/** + * @brief Build the managed CSS block for the current window theme. + * @details The block exposes all Theme dialog color values as CSS variables and + * uses selector rules broad enough for the application chrome, editor, + * popovers, dialogs, search, diagnostics, previews, and tab state. + * @param win The source window. + * @return Owned CSS text for the managed block. + */ +static char *build_managed_theme_css(EditorWindow *win) { + if (!win) return NULL; + GString *css = g_string_new("/* GRAPTOS THEME BEGIN */\n"); + if (!css) return NULL; + + append_theme_define(css, "editor_bg", win->editor_bg_color, "#181a1f"); + append_theme_define(css, "editor_fg", win->editor_fg_color, "#d4d4d4"); + append_theme_define(css, "editor_gutter_bg", win->editor_gutter_bg_color, "#181a1f"); + append_theme_define(css, "editor_gutter_fg", win->editor_gutter_fg_color, "#8b949e"); + append_theme_define(css, "editor_current_line_bg", win->editor_current_line_bg_color, "#20232b"); + append_theme_define(css, "editor_selection_bg", win->editor_selection_bg_color, "#3a405c"); + append_theme_define(css, "editor_selection_fg", win->editor_selection_fg_color, "#ffffff"); + append_theme_define(css, "editor_cursor", win->editor_cursor_color, "#d4d4d4"); + append_theme_define(css, "sidebar_bg", win->sidebar_bg_color, "#111318"); + append_theme_define(css, "tabbar_bg", win->tabbar_bg_color, "#111318"); + append_theme_define(css, "tabbar_fg", win->tabbar_fg_color, "#d4d4d4"); + append_theme_define(css, "tabbar_border", win->tabbar_border_color, "#00000000"); + append_theme_define(css, "tab_active_bg", win->tab_active_bg_color, "#20232b"); + append_theme_define(css, "tab_active_fg", win->tab_active_fg_color, "#ffffff"); + append_theme_define(css, "tab_active_border", win->tab_active_border_color, "#89b4fa"); + append_theme_define(css, "tab_tiled_indicator", win->tab_tiled_indicator_color, "#89b4fa"); + append_theme_define(css, "topbar_bg", win->topbar_bg_color, "#111318"); + append_theme_define(css, "topbar_fg", win->topbar_fg_color, "#d4d4d4"); + append_theme_define(css, "bottombar_bg", win->bottombar_bg_color, "#111318"); + append_theme_define(css, "bottombar_fg", win->bottombar_fg_color, "#d4d4d4"); + append_theme_define(css, "status_error", win->status_error_color, "#ff6b6b"); + append_theme_define(css, "button_bg", win->button_bg_color, "#181a1f"); + append_theme_define(css, "button_fg", win->button_fg_color, "#d4d4d4"); + append_theme_define(css, "button_hover_bg", win->button_hover_bg_color, "#2a2e3d"); + append_theme_define(css, "button_active_bg", win->button_active_bg_color, "#31364a"); + append_theme_define(css, "input_bg", win->input_bg_color, "#111318"); + append_theme_define(css, "input_fg", win->input_fg_color, "#d4d4d4"); + append_theme_define(css, "input_border", win->input_border_color, "#3a4050"); + append_theme_define(css, "project_tree_fg", win->project_tree_fg_color, "#d4d4d4"); + append_theme_define(css, "project_tree_selected_bg", win->project_tree_selected_bg_color, "#2a2e3d"); + append_theme_define(css, "project_tree_selected_fg", win->project_tree_selected_fg_color, "#ffffff"); + append_theme_define(css, "git_status_modified", win->git_status_modified_color, "#f9c74f"); + append_theme_define(css, "git_status_added", win->git_status_added_color, "#57cc99"); + append_theme_define(css, "git_status_deleted", win->git_status_deleted_color, "#ff6b6b"); + append_theme_define(css, "git_status_renamed", win->git_status_renamed_color, "#4cc9f0"); + append_theme_define(css, "git_status_conflict", win->git_status_conflict_color, "#ff4d6d"); + append_theme_define(css, "git_status_untracked", win->git_status_untracked_color, "#77bdfb"); + append_theme_define(css, "git_status_staged", win->git_status_staged_color, "#cba6f7"); + append_theme_define(css, "scroll_preview_bg", win->scroll_preview_bg_color, "#111318"); + append_theme_define(css, "scroll_preview_fg", win->scroll_preview_fg_color, "#8b949e"); + append_theme_define(css, "popover_bg", win->popover_bg_color, "#1b1f24"); + append_theme_define(css, "popover_border", win->popover_border_color, "#00000000"); + append_theme_define(css, "tooltip_bg", win->tooltip_bg_color, "#1b1f24"); + append_theme_define(css, "tooltip_fg", win->tooltip_fg_color, "#d4d4d4"); + append_theme_define(css, "tooltip_border", win->tooltip_border_color, "#00000000"); + append_theme_define(css, "ref_popover_bg", win->ref_popover_bg_color, "#1b1f24"); + append_theme_define(css, "ref_popover_fg", win->ref_popover_fg_color, "#d4d4d4"); + append_theme_define(css, "ref_popover_heading", win->ref_popover_heading_color, "#cba6f7"); + append_theme_define(css, "ref_popover_title", win->ref_popover_title_color, "#89dceb"); + append_theme_define(css, "ref_popover_kind", win->ref_popover_kind_color, "#a6adc8"); + append_theme_define(css, "ref_popover_snippet", win->ref_popover_snippet_color, "#d4d4d4"); + append_theme_define(css, "ref_popover_hover_bg", win->ref_popover_hover_bg_color, "#2a2e3d"); + append_theme_define(css, "ref_popover_hover_fg", win->ref_popover_hover_fg_color, "#ffffff"); + append_theme_define(css, "completion_popover_bg", win->completion_popover_bg_color, "#1b1f24"); + append_theme_define(css, "completion_popover_fg", win->completion_popover_fg_color, "#d4d4d4"); + append_theme_define(css, "completion_popover_detail", win->completion_popover_detail_color, "#a6adc8"); + append_theme_define(css, "completion_selection_bg", win->completion_selection_bg_color, "#89b4fa"); + append_theme_define(css, "completion_selection_fg", win->completion_selection_fg_color, "#11111b"); + append_theme_define(css, "dialog_bg", win->dialog_bg_color, "#1b1f24"); + append_theme_define(css, "dialog_fg", win->dialog_fg_color, "#d4d4d4"); + append_theme_define(css, "dialog_border", win->dialog_border_color, "#00000000"); + append_theme_define(css, "dialog_title", win->dialog_title_color, "#ffffff"); + append_theme_define(css, "dialog_body", win->dialog_body_color, "#d4d4d4"); + append_theme_define(css, "dialog_muted", win->dialog_muted_color, "#a6adc8"); + append_theme_define(css, "dialog_output", win->dialog_output_color, "#d4d4d4"); + append_theme_define(css, "git_output_bg", win->git_output_bg_color, "#1b1f24"); + append_theme_define(css, "dialog_action", win->dialog_action_color, "#d4d4d4"); + append_theme_define(css, "dialog_destructive_action", win->dialog_destructive_action_color, "#ff6b6b"); + append_theme_define(css, "dialog_input_fg", win->dialog_input_fg_color, "#d4d4d4"); + append_theme_define(css, "dialog_input_bg", win->dialog_input_bg_color, "#111318"); + append_theme_define(css, "search_match_bg", win->search_match_bg_color, "#515c7a"); + append_theme_define(css, "search_match_fg", win->search_match_fg_color, "#ffffff"); + append_theme_define(css, "diagnostic_warning_bg", win->diagnostic_warning_bg_color, "#5f4b24"); + append_theme_define(css, "diagnostic_warning_fg", win->diagnostic_warning_fg_color, "#ffd166"); + append_theme_define(css, "codex_preview_bg", win->codex_preview_bg_color, "#1b1f24"); + append_theme_define(css, "codex_preview_fg", win->codex_preview_fg_color, "#d4d4d4"); + append_theme_define(css, "codex_prompt_bg", win->codex_prompt_bg_color, "#111318"); + g_string_append_c(css, '\n'); + append_theme_font_meta(css, "ui", win->ui_font); + append_theme_font_meta(css, "editor", win->editor_font); + append_theme_font_meta(css, "preview", win->preview_font); + append_theme_font_meta(css, "terminal", win->terminal_font); + append_theme_font_meta(css, "code", win->code_font); + g_string_append(css, + "\n" + "window.graptos-window, window.graptos-window > contents,\n" + ".graptos-root, .graptos-root > box, .graptos-tab-page,\n" + ".graptos-editor-area, .graptos-editor-overlay,\n" + ".graptos-editor-content {\n" + " background: @graptos_editor_bg;\n" + " background-color: @graptos_editor_bg;\n" + " color: @graptos_editor_fg;\n" + "}\n\n" + ".graptos-editor, .graptos-editor text,\n" + "textview.graptos-editor, textview.graptos-editor text,\n" + "sourceview.graptos-editor, sourceview.graptos-editor text {\n" + " background: @graptos_editor_bg;\n" + " background-color: @graptos_editor_bg;\n" + " color: @graptos_editor_fg;\n" + " caret-color: @graptos_editor_cursor;\n" + "}\n\n" + "sourceview.graptos-editor gutter,\n" + "sourceview.graptos-editor gutter * {\n" + " background: @graptos_editor_gutter_bg;\n" + " background-color: @graptos_editor_gutter_bg;\n" + " color: @graptos_editor_gutter_fg;\n" + "}\n\n" + "sourceview.graptos-editor text selection {\n" + " background: @graptos_editor_selection_bg;\n" + " background-color: @graptos_editor_selection_bg;\n" + " color: @graptos_editor_selection_fg;\n" + "}\n\n" + ".graptos-current-line { background: @graptos_editor_current_line_bg; }\n" + ".graptos-top { background: @graptos_topbar_bg; color: @graptos_topbar_fg; }\n" + ".graptos-bottom, .graptos-search-panel, .graptos-tool-panel {\n" + " background: @graptos_bottombar_bg;\n" + " color: @graptos_bottombar_fg;\n" + "}\n" + ".graptos-bottom label.graptos-status-error,\n" + "label.graptos-status-error { color: @graptos_status_error; }\n\n" + ".graptos-root button {\n" + " background: @graptos_button_bg;\n" + " color: @graptos_button_fg;\n" + "}\n" + ".graptos-root button:hover { background: @graptos_button_hover_bg; }\n" + ".graptos-root button:checked, .graptos-root button:active { background: @graptos_button_active_bg; }\n\n" + ".graptos-root entry, .graptos-root entry text,\n" + ".graptos-root spinbutton, .graptos-root spinbutton text,\n" + ".graptos-root dropdown, .graptos-root combobox {\n" + " background: @graptos_input_bg;\n" + " color: @graptos_input_fg;\n" + " border-color: @graptos_input_border;\n" + "}\n\n" + ".graptos-project-pane, .graptos-project-tree,\n" + ".graptos-project-tree row {\n" + " background: @graptos_sidebar_bg;\n" + " color: @graptos_project_tree_fg;\n" + "}\n" + ".graptos-project-tree row:selected, .graptos-project-tree row:hover {\n" + " background: @graptos_project_tree_selected_bg;\n" + " color: @graptos_project_tree_selected_fg;\n" + "}\n" + ".graptos-project-tree label.graptos-git-status-modified { color: @graptos_git_status_modified; }\n" + ".graptos-project-tree label.graptos-git-status-added { color: @graptos_git_status_added; }\n" + ".graptos-project-tree label.graptos-git-status-deleted { color: @graptos_git_status_deleted; }\n" + ".graptos-project-tree label.graptos-git-status-renamed { color: @graptos_git_status_renamed; }\n" + ".graptos-project-tree label.graptos-git-status-conflict { color: @graptos_git_status_conflict; }\n" + ".graptos-project-tree label.graptos-git-status-untracked { color: @graptos_git_status_untracked; }\n" + ".graptos-project-tree label.graptos-git-status-staged { color: @graptos_git_status_staged; }\n\n" + ".graptos-root notebook > header,\n" + ".graptos-root notebook > header.top > tabs > tab {\n" + " background: @graptos_tabbar_bg;\n" + " color: @graptos_tabbar_fg;\n" + " border-left-color: @graptos_tabbar_border;\n" + " border-right-color: @graptos_tabbar_border;\n" + " border-top-color: @graptos_tabbar_border;\n" + " border-bottom-color: @graptos_tabbar_border;\n" + "}\n" + ".graptos-root notebook > header.top {\n" + " border-color: @graptos_tabbar_border;\n" + " border-bottom-color: @graptos_tab_active_border;\n" + "}\n" + ".graptos-root notebook > header.top > tabs > tab:checked {\n" + " background: @graptos_tab_active_bg;\n" + " color: @graptos_tab_active_fg;\n" + " border-left-color: @graptos_tabbar_border;\n" + " border-right-color: @graptos_tabbar_border;\n" + " border-top-color: @graptos_tabbar_border;\n" + " border-bottom-color: @graptos_tab_active_border;\n" + "}\n" + ".graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; }\n\n"); + g_string_append(css, + ".graptos-minimap, .graptos-minimap text,\n" + ".graptos-preview, .graptos-preview text {\n" + " background: @graptos_scroll_preview_bg;\n" + " color: @graptos_scroll_preview_fg;\n" + "}\n" + ".graptos-codex-preview, .graptos-codex-preview text {\n" + " background: @graptos_codex_preview_bg;\n" + " color: @graptos_codex_preview_fg;\n" + "}\n" + ".graptos-codex-prompt, .graptos-codex-prompt text { background: @graptos_codex_prompt_bg; }\n\n" + ".graptos-codex-approval {\n" + " background: @graptos_diagnostic_warning_bg;\n" + " border-color: @graptos_diagnostic_warning_fg;\n" + "}\n" + ".graptos-codex-review {\n" + " background: @graptos_search_match_bg;\n" + " border-color: @graptos_search_match_fg;\n" + "}\n\n" + "popover.graptos-context-popover, popover.graptos-tools-popover,\n" + "popover.graptos-completion-popover, popover.graptos-hover-popover,\n" + "popover.graptos-context-popover contents, popover.graptos-tools-popover contents,\n" + "popover.graptos-completion-popover contents, popover.graptos-hover-popover contents {\n" + " background: @graptos_popover_bg;\n" + " border-color: @graptos_popover_border;\n" + "}\n" + "tooltip, tooltip.background, tooltip > box {\n" + " background: @graptos_tooltip_bg;\n" + " color: @graptos_tooltip_fg;\n" + " border-color: @graptos_tooltip_border;\n" + "}\n\n" + ".graptos-completion-list, .graptos-completion-list row {\n" + " background: @graptos_completion_popover_bg;\n" + " color: @graptos_completion_popover_fg;\n" + "}\n" + ".graptos-completion-detail { color: @graptos_completion_popover_detail; }\n" + ".graptos-completion-list row:selected,\n" + ".graptos-completion-list row:hover {\n" + " background: @graptos_completion_selection_bg;\n" + " color: @graptos_completion_selection_fg;\n" + "}\n\n" + ".graptos-ref-list, .graptos-ref-list row {\n" + " background: @graptos_ref_popover_bg;\n" + " color: @graptos_ref_popover_fg;\n" + "}\n" + ".graptos-ref-heading { color: @graptos_ref_popover_heading; }\n" + ".graptos-ref-title { color: @graptos_ref_popover_title; }\n" + ".graptos-ref-kind { color: @graptos_ref_popover_kind; }\n" + ".graptos-ref-snippet { color: @graptos_ref_popover_snippet; }\n" + ".graptos-ref-list row:hover { background: @graptos_ref_popover_hover_bg; color: @graptos_ref_popover_hover_fg; }\n\n" + "window.graptos-window .graptos-root.graptos-dialog-root,\n" + "window.graptos-window .graptos-dialog-output,\n" + "window.graptos-window .graptos-dialog-output text {\n" + " background: @graptos_dialog_bg;\n" + " color: @graptos_dialog_fg;\n" + " border-color: @graptos_dialog_border;\n" + "}\n" + ".graptos-window .graptos-dialog-title,\n" + ".graptos-window .graptos-menu-title { color: @graptos_dialog_title; }\n" + ".graptos-window .graptos-dialog-body { color: @graptos_dialog_body; }\n" + ".graptos-window .graptos-dialog-muted { color: @graptos_dialog_muted; }\n" + ".graptos-window .graptos-dialog-output { color: @graptos_dialog_output; }\n" + ".graptos-window .graptos-git-dialog-output { background: @graptos_git_output_bg; }\n" + ".graptos-window .graptos-dialog-action { color: @graptos_dialog_action; }\n" + ".graptos-window .graptos-dialog-action-destructive { color: @graptos_dialog_destructive_action; }\n" + ".graptos-window .graptos-root.graptos-dialog-root entry,\n" + ".graptos-window .graptos-root.graptos-dialog-root entry text {\n" + " background: @graptos_dialog_input_bg;\n" + " color: @graptos_dialog_input_fg;\n" + "}\n\n" + ".graptos-search-match { background: @graptos_search_match_bg; color: @graptos_search_match_fg; }\n" + ".graptos-diagnostic-warning { background: @graptos_diagnostic_warning_bg; color: @graptos_diagnostic_warning_fg; }\n" + "/* GRAPTOS THEME END */\n"); + return g_string_free(css, FALSE); +} + +/** + * @brief Extract one CSS variable from a theme file. + * @details The parser intentionally reads only Graptoς-managed variables. Free + * CSS remains valid but is not projected into structured controls. + * @param css Theme CSS text. + * @param name Variable name without the `graptos_` prefix. + * @param slot Destination string slot. + * @return TRUE when a valid color was loaded. + */ +static gboolean load_theme_define(const char *css, + const char *name, + char **slot) { + if (!css || !name || !slot) return FALSE; + g_autofree char *needle = g_strdup_printf("@define-color graptos_%s", name); + char *pos = strstr(css, needle); + if (!pos) return FALSE; + pos += strlen(needle); + while (*pos == ' ' || *pos == '\t') pos++; + char *end = pos; + while (*end && *end != ';' && *end != '\n' && *end != '\r' && + *end != ' ' && *end != '\t') { + end++; + } + if (end <= pos) return FALSE; + g_autofree char *value = g_strndup(pos, (gsize)(end - pos)); + GdkRGBA rgba; + if (!value || !gdk_rgba_parse(&rgba, value)) return FALSE; + replace_config_string(slot, value); + return TRUE; +} + +/** + * @brief Extract one font metadata value from a CSS theme file. + * @details Font values are stored as comments so hand-written CSS can still + * override font selectors without breaking the structured dialog. + * @param css Theme CSS text. + * @param name Metadata key. + * @param slot Destination string slot. + */ +static void load_theme_font_meta(const char *css, + const char *name, + char **slot) { + if (!css || !name || !slot) return; + g_autofree char *needle = g_strdup_printf("/* @graptos-%s-font:", name); + char *pos = strstr(css, needle); + if (!pos) return; + pos += strlen(needle); + char *end = strstr(pos, "*/"); + if (!end) return; + g_autofree char *value = g_strndup(pos, (gsize)(end - pos)); + if (!value) return; + g_strstrip(value); + replace_config_string(slot, value); +} + +/** + * @brief Load theme values from a Graptoς CSS theme file. + * @details CSS is authoritative once present. Legacy config values may seed + * the window before this call, but values parsed here win. + * @param win The window receiving theme values. + * @param path The CSS theme file path. + * @return TRUE when the CSS file was read. + */ +gboolean graptos_theme_css_load_into_window(EditorWindow *win, + const char *path) { + if (!win || !path || path[0] == '\0') return FALSE; + g_autofree char *css = NULL; + gsize length = 0u; + g_autoptr(GError) error = NULL; + if (!g_file_get_contents(path, &css, &length, &error) || !css) return FALSE; + +#define LOAD_THEME_VAR(name, field) \ + (void)load_theme_define(css, name, &win->field) + LOAD_THEME_VAR("editor_bg", editor_bg_color); + LOAD_THEME_VAR("editor_fg", editor_fg_color); + LOAD_THEME_VAR("editor_gutter_bg", editor_gutter_bg_color); + LOAD_THEME_VAR("editor_gutter_fg", editor_gutter_fg_color); + LOAD_THEME_VAR("editor_current_line_bg", editor_current_line_bg_color); + LOAD_THEME_VAR("editor_selection_bg", editor_selection_bg_color); + LOAD_THEME_VAR("editor_selection_fg", editor_selection_fg_color); + LOAD_THEME_VAR("editor_cursor", editor_cursor_color); + LOAD_THEME_VAR("sidebar_bg", sidebar_bg_color); + LOAD_THEME_VAR("tabbar_bg", tabbar_bg_color); + LOAD_THEME_VAR("tabbar_fg", tabbar_fg_color); + LOAD_THEME_VAR("tabbar_border", tabbar_border_color); + LOAD_THEME_VAR("tab_active_bg", tab_active_bg_color); + LOAD_THEME_VAR("tab_active_fg", tab_active_fg_color); + LOAD_THEME_VAR("tab_active_border", tab_active_border_color); + LOAD_THEME_VAR("tab_tiled_indicator", tab_tiled_indicator_color); + LOAD_THEME_VAR("topbar_bg", topbar_bg_color); + LOAD_THEME_VAR("topbar_fg", topbar_fg_color); + LOAD_THEME_VAR("bottombar_bg", bottombar_bg_color); + LOAD_THEME_VAR("bottombar_fg", bottombar_fg_color); + LOAD_THEME_VAR("status_error", status_error_color); + LOAD_THEME_VAR("button_bg", button_bg_color); + LOAD_THEME_VAR("button_fg", button_fg_color); + LOAD_THEME_VAR("button_hover_bg", button_hover_bg_color); + LOAD_THEME_VAR("button_active_bg", button_active_bg_color); + LOAD_THEME_VAR("input_bg", input_bg_color); + LOAD_THEME_VAR("input_fg", input_fg_color); + LOAD_THEME_VAR("input_border", input_border_color); + LOAD_THEME_VAR("project_tree_fg", project_tree_fg_color); + LOAD_THEME_VAR("project_tree_selected_bg", project_tree_selected_bg_color); + LOAD_THEME_VAR("project_tree_selected_fg", project_tree_selected_fg_color); + LOAD_THEME_VAR("git_status_modified", git_status_modified_color); + LOAD_THEME_VAR("git_status_added", git_status_added_color); + LOAD_THEME_VAR("git_status_deleted", git_status_deleted_color); + LOAD_THEME_VAR("git_status_renamed", git_status_renamed_color); + LOAD_THEME_VAR("git_status_conflict", git_status_conflict_color); + LOAD_THEME_VAR("git_status_untracked", git_status_untracked_color); + LOAD_THEME_VAR("git_status_staged", git_status_staged_color); + LOAD_THEME_VAR("scroll_preview_bg", scroll_preview_bg_color); + LOAD_THEME_VAR("scroll_preview_fg", scroll_preview_fg_color); + LOAD_THEME_VAR("popover_bg", popover_bg_color); + LOAD_THEME_VAR("popover_border", popover_border_color); + LOAD_THEME_VAR("tooltip_bg", tooltip_bg_color); + LOAD_THEME_VAR("tooltip_fg", tooltip_fg_color); + LOAD_THEME_VAR("tooltip_border", tooltip_border_color); + LOAD_THEME_VAR("ref_popover_bg", ref_popover_bg_color); + LOAD_THEME_VAR("ref_popover_fg", ref_popover_fg_color); + LOAD_THEME_VAR("ref_popover_heading", ref_popover_heading_color); + LOAD_THEME_VAR("ref_popover_title", ref_popover_title_color); + LOAD_THEME_VAR("ref_popover_kind", ref_popover_kind_color); + LOAD_THEME_VAR("ref_popover_snippet", ref_popover_snippet_color); + LOAD_THEME_VAR("ref_popover_hover_bg", ref_popover_hover_bg_color); + LOAD_THEME_VAR("ref_popover_hover_fg", ref_popover_hover_fg_color); + LOAD_THEME_VAR("completion_popover_bg", completion_popover_bg_color); + LOAD_THEME_VAR("completion_popover_fg", completion_popover_fg_color); + LOAD_THEME_VAR("completion_popover_detail", completion_popover_detail_color); + LOAD_THEME_VAR("completion_selection_bg", completion_selection_bg_color); + LOAD_THEME_VAR("completion_selection_fg", completion_selection_fg_color); + if (!strstr(css, "graptos_tab_active_border")) { + replace_config_string(&win->tab_active_border_color, + win->completion_selection_bg_color); + } + if (!strstr(css, "graptos_tab_tiled_indicator")) { + replace_config_string(&win->tab_tiled_indicator_color, + win->completion_selection_bg_color); + } + LOAD_THEME_VAR("dialog_bg", dialog_bg_color); + LOAD_THEME_VAR("dialog_fg", dialog_fg_color); + LOAD_THEME_VAR("dialog_border", dialog_border_color); + LOAD_THEME_VAR("dialog_title", dialog_title_color); + LOAD_THEME_VAR("dialog_body", dialog_body_color); + LOAD_THEME_VAR("dialog_muted", dialog_muted_color); + LOAD_THEME_VAR("dialog_output", dialog_output_color); + LOAD_THEME_VAR("git_output_bg", git_output_bg_color); + LOAD_THEME_VAR("dialog_action", dialog_action_color); + LOAD_THEME_VAR("dialog_destructive_action", dialog_destructive_action_color); + LOAD_THEME_VAR("dialog_input_fg", dialog_input_fg_color); + LOAD_THEME_VAR("dialog_input_bg", dialog_input_bg_color); + LOAD_THEME_VAR("search_match_bg", search_match_bg_color); + LOAD_THEME_VAR("search_match_fg", search_match_fg_color); + LOAD_THEME_VAR("diagnostic_warning_bg", diagnostic_warning_bg_color); + LOAD_THEME_VAR("diagnostic_warning_fg", diagnostic_warning_fg_color); + LOAD_THEME_VAR("codex_preview_bg", codex_preview_bg_color); + LOAD_THEME_VAR("codex_preview_fg", codex_preview_fg_color); + LOAD_THEME_VAR("codex_prompt_bg", codex_prompt_bg_color); +#undef LOAD_THEME_VAR + load_theme_font_meta(css, "ui", &win->ui_font); + load_theme_font_meta(css, "editor", &win->editor_font); + load_theme_font_meta(css, "preview", &win->preview_font); + load_theme_font_meta(css, "terminal", &win->terminal_font); + load_theme_font_meta(css, "code", &win->code_font); + return TRUE; +} + +/** + * @brief Save the window theme into a Graptoς CSS theme file. + * @details User CSS outside the managed block survives Theme dialog saves. + * When no block exists, the managed block is appended after any + * existing hand-written CSS. + * @param win The window whose theme values are written. + * @param path The CSS theme file path. + * @param preserve_custom TRUE to keep CSS outside the managed block. + * @return TRUE when the CSS reached disk. + */ +gboolean graptos_theme_css_save_from_window(EditorWindow *win, + const char *path, + gboolean preserve_custom) { + if (!win || !path || path[0] == '\0') return FALSE; + g_autofree char *managed = build_managed_theme_css(win); + if (!managed) return FALSE; + + g_autofree char *existing = NULL; + gsize existing_len = 0u; + (void)g_file_get_contents(path, &existing, &existing_len, NULL); + + GString *out = g_string_new(NULL); + if (!out) return FALSE; + const char *begin = existing ? strstr(existing, "/* GRAPTOS THEME BEGIN */") : NULL; + const char *end = existing ? strstr(existing, "/* GRAPTOS THEME END */") : NULL; + if (preserve_custom && existing && begin && end && end >= begin) { + end += strlen("/* GRAPTOS THEME END */"); + while (*end == '\r' || *end == '\n') end++; + g_string_append_len(out, existing, (gssize)(begin - existing)); + g_string_append(out, managed); + if (*end != '\0') { + if (out->len > 0u && out->str[out->len - 1u] != '\n') g_string_append_c(out, '\n'); + g_string_append(out, end); + } + } else if (preserve_custom && existing && existing[0] != '\0' && !begin) { + g_string_append(out, existing); + if (out->str[out->len - 1u] != '\n') g_string_append_c(out, '\n'); + g_string_append_c(out, '\n'); + g_string_append(out, managed); + } else { + g_string_append(out, managed); + } + + g_autofree char *dir = g_path_get_dirname(path); + gboolean ok = dir && g_mkdir_with_parents(dir, 0700) == 0 && + g_file_set_contents(path, out->str, (gssize)out->len, NULL); + g_string_free(out, TRUE); + return ok; +} + /** * @brief Populate a key file with the complete known Graptoς config shape. * @details This helper is the single list of keys Graptoς knows how to write. @@ -196,89 +726,7 @@ static gboolean write_config_key_file(EditorWindow *win, static void populate_config_key_file(EditorWindow *win, GKeyFile *key_file) { if (!win || !key_file) return; - save_color(key_file, "background_color", win->editor_bg_color); - save_color(key_file, "foreground_color", win->editor_fg_color); - save_color(key_file, "editor_gutter_background_color", win->editor_gutter_bg_color); - save_color(key_file, "editor_gutter_foreground_color", win->editor_gutter_fg_color); - save_color(key_file, "editor_current_line_background_color", win->editor_current_line_bg_color); - save_color(key_file, "editor_selection_background_color", win->editor_selection_bg_color); - save_color(key_file, "editor_selection_foreground_color", win->editor_selection_fg_color); - save_color(key_file, "editor_cursor_color", win->editor_cursor_color); - save_color(key_file, "sidebar_background_color", win->sidebar_bg_color); - save_color(key_file, "tabbar_background_color", win->tabbar_bg_color); - save_color(key_file, "tabbar_foreground_color", win->tabbar_fg_color); - save_color(key_file, "tab_active_background_color", win->tab_active_bg_color); - save_color(key_file, "tab_active_foreground_color", win->tab_active_fg_color); - save_color(key_file, "topbar_background_color", win->topbar_bg_color); - save_color(key_file, "topbar_foreground_color", win->topbar_fg_color); - save_color(key_file, "bottombar_background_color", win->bottombar_bg_color); - save_color(key_file, "bottombar_foreground_color", win->bottombar_fg_color); - save_color(key_file, "status_error_color", win->status_error_color); - save_color(key_file, "button_background_color", win->button_bg_color); - save_color(key_file, "button_foreground_color", win->button_fg_color); - save_color(key_file, "button_hover_background_color", win->button_hover_bg_color); - save_color(key_file, "button_active_background_color", win->button_active_bg_color); - save_color(key_file, "input_background_color", win->input_bg_color); - save_color(key_file, "input_foreground_color", win->input_fg_color); - save_color(key_file, "input_border_color", win->input_border_color); - save_color(key_file, "project_tree_foreground_color", win->project_tree_fg_color); - save_color(key_file, "project_tree_selected_background_color", win->project_tree_selected_bg_color); - save_color(key_file, "project_tree_selected_foreground_color", win->project_tree_selected_fg_color); - save_color(key_file, "git_status_modified_color", win->git_status_modified_color); - save_color(key_file, "git_status_added_color", win->git_status_added_color); - save_color(key_file, "git_status_deleted_color", win->git_status_deleted_color); - save_color(key_file, "git_status_renamed_color", win->git_status_renamed_color); - save_color(key_file, "git_status_conflict_color", win->git_status_conflict_color); - save_color(key_file, "git_status_untracked_color", win->git_status_untracked_color); - save_color(key_file, "git_status_staged_color", win->git_status_staged_color); - save_color(key_file, "scroll_preview_background_color", win->scroll_preview_bg_color); - save_color(key_file, "scroll_preview_foreground_color", win->scroll_preview_fg_color); - save_color(key_file, "popover_background_color", win->popover_bg_color); - save_color(key_file, "popover_border_color", win->popover_border_color); - save_color(key_file, "tooltip_background_color", win->tooltip_bg_color); - save_color(key_file, "tooltip_foreground_color", win->tooltip_fg_color); - save_color(key_file, "tooltip_border_color", win->tooltip_border_color); - save_color(key_file, "ref_popover_background_color", win->ref_popover_bg_color); - save_color(key_file, "ref_popover_foreground_color", win->ref_popover_fg_color); - save_color(key_file, "ref_popover_heading_color", win->ref_popover_heading_color); - save_color(key_file, "ref_popover_title_color", win->ref_popover_title_color); - save_color(key_file, "ref_popover_kind_color", win->ref_popover_kind_color); - save_color(key_file, "ref_popover_snippet_color", win->ref_popover_snippet_color); - save_color(key_file, "ref_popover_hover_background_color", win->ref_popover_hover_bg_color); - save_color(key_file, "ref_popover_hover_foreground_color", win->ref_popover_hover_fg_color); - save_color(key_file, "autocomplete_popover_background_color", win->completion_popover_bg_color); - save_color(key_file, "autocomplete_popover_foreground_color", win->completion_popover_fg_color); - save_color(key_file, "autocomplete_popover_detail_color", win->completion_popover_detail_color); - save_color(key_file, "autocomplete_selection_background_color", win->completion_selection_bg_color); - save_color(key_file, "autocomplete_selection_foreground_color", win->completion_selection_fg_color); - save_color(key_file, "dialog_background_color", win->dialog_bg_color); - save_color(key_file, "dialog_foreground_color", win->dialog_fg_color); - save_color(key_file, "dialog_border_color", win->dialog_border_color); - save_color(key_file, "dialog_title_color", win->dialog_title_color); - save_color(key_file, "dialog_body_color", win->dialog_body_color); - save_color(key_file, "dialog_muted_color", win->dialog_muted_color); - save_color(key_file, "dialog_output_color", win->dialog_output_color); - save_color(key_file, "git_output_background_color", win->git_output_bg_color); - save_color(key_file, "dialog_action_color", win->dialog_action_color); - save_color(key_file, "dialog_destructive_action_color", - win->dialog_destructive_action_color); - save_color(key_file, "dialog_input_foreground_color", - win->dialog_input_fg_color); - save_color(key_file, "dialog_input_background_color", - win->dialog_input_bg_color); - save_color(key_file, "search_match_background_color", win->search_match_bg_color); - save_color(key_file, "search_match_foreground_color", win->search_match_fg_color); - save_color(key_file, "diagnostic_warning_background_color", win->diagnostic_warning_bg_color); - save_color(key_file, "diagnostic_warning_foreground_color", win->diagnostic_warning_fg_color); - save_color(key_file, "codex_preview_background_color", win->codex_preview_bg_color); - save_color(key_file, "codex_preview_foreground_color", win->codex_preview_fg_color); - save_color(key_file, "codex_prompt_background_color", win->codex_prompt_bg_color); - save_string(key_file, "ui_font", win->ui_font ? win->ui_font : ""); - save_string(key_file, "editor_font", win->editor_font ? win->editor_font : ""); - save_string(key_file, "preview_font", win->preview_font ? win->preview_font : ""); - save_string(key_file, "terminal_font", win->terminal_font ? win->terminal_font : ""); - save_string(key_file, "code_font", win->code_font ? win->code_font : ""); - g_key_file_set_boolean(key_file, "Editor", "use_system_interface_font", win->use_system_interface_font); + save_string(key_file, "theme_css_path", win->theme_css_path ? win->theme_css_path : ""); g_key_file_set_boolean(key_file, "Editor", "autocomplete_enabled", win->autocomplete_enabled); g_key_file_set_boolean(key_file, "Editor", "regex_tester_enabled", win->regex_tester_enabled); g_key_file_set_boolean(key_file, "Editor", "diagnostics_enabled", win->diagnostics_enabled); @@ -300,71 +748,6 @@ static void populate_config_key_file(EditorWindow *win, GKeyFile *key_file) { g_key_file_set_boolean(key_file, "Editor", "use_yaml_style_overrides", win->use_yaml_style_overrides); } -/** - * @brief Copy one default key only when the user config does not define it. - * @details Missing-key migration must be conservative. A present value stays - * untouched even when it is unusual, because manual config edits are - * user ownership. - * @param target The user config being migrated. - * @param defaults The current complete default/effective config. - * @param group The key group to inspect. - * @param key The key to copy if absent. - * @return TRUE when a new key was added; otherwise FALSE. - */ -static gboolean copy_missing_config_key(GKeyFile *target, - GKeyFile *defaults, - const char *group, - const char *key) { - if (!target || !defaults || !group || !key) return FALSE; - if (g_key_file_has_key(target, group, key, NULL)) return FALSE; - - g_autoptr(GError) error = NULL; - g_autofree char *value = g_key_file_get_value(defaults, group, key, &error); - if (error || !value) return FALSE; - - g_key_file_set_value(target, group, key, value); - return TRUE; -} - -/** - * @brief Backfill missing config keys after loading an existing file. - * @details New builds add new config keys over time. This scans the loaded file - * and appends only absent keys using the effective values already - * loaded into the window, so existing user choices remain unchanged. - * @param win The window whose effective settings provide migration defaults. - * @param key_file The loaded user config. - * @param path The config path to update when migration changed the file. - */ -static void backfill_missing_config_keys(EditorWindow *win, - GKeyFile *key_file, - const char *path) { - if (!win || !key_file || !path) return; - - g_autoptr(GKeyFile) defaults = g_key_file_new(); - populate_config_key_file(win, defaults); - - gboolean changed = FALSE; - gsize group_count = 0u; - gchar **groups = g_key_file_get_groups(defaults, &group_count); - for (gsize group_index = 0u; group_index < group_count; group_index++) { - gsize key_count = 0u; - gchar **keys = g_key_file_get_keys(defaults, groups[group_index], &key_count, NULL); - for (gsize key_index = 0u; key_index < key_count; key_index++) { - changed |= copy_missing_config_key(key_file, - defaults, - groups[group_index], - keys[key_index]); - } - g_strfreev(keys); - } - g_strfreev(groups); - - if (!changed) return; - if (write_config_key_file(win, key_file, path) && win->debug_mode) { - g_message("Config migration: added missing keys to %s", path); - } -} - /** * @brief Replace removed legacy bundled font names with current defaults. * @details Configuration values are user data, not internal constants. The comment makes the fallback path explicit so missing keys do not overwrite intentional manual edits. @@ -403,6 +786,9 @@ void graptos_config_load(EditorWindow *win) { g_autofree char *path = graptos_config_path(); if (!path) return; if (!g_file_test(path, G_FILE_TEST_EXISTS)) { + if (win->theme_css_path && win->theme_css_path[0] != '\0') { + (void)graptos_theme_css_save_from_window(win, win->theme_css_path, FALSE); + } graptos_config_save(win); return; } @@ -424,8 +810,11 @@ void graptos_config_load(EditorWindow *win) { load_color(key_file, "sidebar_background_color", &win->sidebar_bg_color); load_color(key_file, "tabbar_background_color", &win->tabbar_bg_color); load_color(key_file, "tabbar_foreground_color", &win->tabbar_fg_color); + load_color(key_file, "tabbar_border_color", &win->tabbar_border_color); load_color(key_file, "tab_active_background_color", &win->tab_active_bg_color); load_color(key_file, "tab_active_foreground_color", &win->tab_active_fg_color); + load_color(key_file, "tab_active_border_color", &win->tab_active_border_color); + load_color(key_file, "tab_tiled_indicator_color", &win->tab_tiled_indicator_color); load_color(key_file, "topbar_background_color", &win->topbar_bg_color); load_color(key_file, "topbar_foreground_color", &win->topbar_fg_color); load_color(key_file, "bottombar_background_color", &win->bottombar_bg_color); @@ -495,6 +884,24 @@ void graptos_config_load(EditorWindow *win) { load_string(key_file, "preview_font", &win->preview_font); load_string(key_file, "terminal_font", &win->terminal_font); load_string(key_file, "code_font", &win->code_font); + /** + * @brief Resolve the active CSS theme path from new and legacy keys. + * @details CSS is now the primary theme source. The old custom_css_path key + * is accepted only as a migration alias so existing config files do + * not lose their selected theme file. + */ + if (g_key_file_has_key(key_file, "Editor", "theme_css_path", NULL)) { + load_string(key_file, "theme_css_path", &win->theme_css_path); + } else if (g_key_file_has_key(key_file, "Editor", "custom_css_path", NULL)) { + load_string(key_file, "custom_css_path", &win->theme_css_path); + } else { + g_clear_pointer(&win->theme_css_path, g_free); + win->theme_css_path = theme_css_default_path(); + } + if (!win->theme_css_path || win->theme_css_path[0] == '\0') { + g_clear_pointer(&win->theme_css_path, g_free); + win->theme_css_path = theme_css_default_path(); + } migrate_removed_font(&win->ui_font); migrate_removed_font(&win->editor_font); migrate_removed_font(&win->preview_font); @@ -523,16 +930,21 @@ void graptos_config_load(EditorWindow *win) { win->use_yaml_style_overrides = parse_bool(key_file, "use_yaml_style_overrides", win->use_yaml_style_overrides); if (!win->codex_preview_bg_color) { - win->codex_preview_bg_color = g_strdup(win->editor_bg_color); + win->codex_preview_bg_color = g_strdup("#1b1f24"); } if (!win->codex_preview_fg_color) { win->codex_preview_fg_color = g_strdup("#d4d4d4"); } if (!win->codex_prompt_bg_color) { - win->codex_prompt_bg_color = g_strdup(win->editor_bg_color); + win->codex_prompt_bg_color = g_strdup("#111318"); } - - backfill_missing_config_keys(win, key_file, path); + if (win->theme_css_path && win->theme_css_path[0] != '\0' && + g_file_test(win->theme_css_path, G_FILE_TEST_IS_REGULAR)) { + (void)graptos_theme_css_load_into_window(win, win->theme_css_path); + } else if (win->theme_css_path && win->theme_css_path[0] != '\0') { + (void)graptos_theme_css_save_from_window(win, win->theme_css_path, FALSE); + } + graptos_config_save(win); } /** diff --git a/src/config.h b/src/config.h index 4753313..129214d 100644 --- a/src/config.h +++ b/src/config.h @@ -29,5 +29,29 @@ void graptos_config_save(EditorWindow *win); * @return The resolved value for the caller, or NULL when no suitable value is available. */ char *graptos_config_path(void); +/** + * @brief Load theme values from a Graptoς CSS theme file. + * @details CSS is the primary theme source. This parser reads the managed + * `@define-color graptos_*` values and font metadata comments into the + * window's in-memory theme fields. + * @param win The window receiving theme values. + * @param path The CSS theme file path. + * @return TRUE when the CSS file was read. + */ +gboolean graptos_theme_css_load_into_window(EditorWindow *win, + const char *path); +/** + * @brief Save the window theme into a Graptoς CSS theme file. + * @details The managed theme block is replaced while user CSS outside that + * block is preserved. This keeps the Theme dialog structured without + * taking ownership of hand-written CSS. + * @param win The window whose theme values are written. + * @param path The CSS theme file path. + * @param preserve_custom TRUE to keep CSS outside the managed block. + * @return TRUE when the CSS reached disk. + */ +gboolean graptos_theme_css_save_from_window(EditorWindow *win, + const char *path, + gboolean preserve_custom); #endif /* GRAPTOS_CONFIG_H */ diff --git a/src/editor/editor_tab_hover_events.inc.c b/src/editor/editor_tab_hover_events.inc.c index 0dfb213..3025004 100644 --- a/src/editor/editor_tab_hover_events.inc.c +++ b/src/editor/editor_tab_hover_events.inc.c @@ -976,11 +976,22 @@ static gboolean selected_text_is_regex(EditorTab *tab, const char *selected) { */ static void ensure_regex_tester_tag(GtkTextBuffer *buffer) { if (!buffer) return; + const char *bg = g_object_get_data(G_OBJECT(buffer), + "graptos-regex-match-bg"); + const char *fg = g_object_get_data(G_OBJECT(buffer), + "graptos-regex-match-fg"); + if (!bg || bg[0] != '#') bg = NULL; + if (!fg || fg[0] != '#') fg = NULL; GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer); - if (gtk_text_tag_table_lookup(table, "graptos-regex-match")) return; + GtkTextTag *tag = gtk_text_tag_table_lookup(table, "graptos-regex-match"); + if (tag) { + g_object_set(tag, "background", bg, "foreground", fg, + "underline", PANGO_UNDERLINE_SINGLE, NULL); + return; + } gtk_text_buffer_create_tag(buffer, "graptos-regex-match", - "background", "#515c7a", - "foreground", "#ffffff", + "background", bg, + "foreground", fg, "underline", PANGO_UNDERLINE_SINGLE, NULL); } @@ -1074,11 +1085,13 @@ static void regex_tester_text_changed(GtkTextBuffer *buffer, gpointer user_data) /** * @brief Create regex tester row. * @details Editor code runs in response to fast input, delayed timeouts, and background language work. The notes here mark the boundary between immediate GTK state and deferred refresh paths so latency fixes do not turn into stale-widget bugs. + * @param tab The editor tab whose theme state provides match colors. * @param pattern The pattern supplied by the caller. * @param test_view_out Output storage filled when the operation can provide a value. * @return The resolved value for the caller, or NULL when no suitable value is available. */ -static GtkWidget *regex_tester_row_new(const char *pattern, +static GtkWidget *regex_tester_row_new(EditorTab *tab, + const char *pattern, GtkWidget **test_view_out) { GtkWidget *row = gtk_list_box_row_new(); gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(row), FALSE); @@ -1118,6 +1131,16 @@ static GtkWidget *regex_tester_row_new(const char *pattern, "graptos-regex-pattern", g_strdup(pattern ? pattern : ""), g_free); + g_object_set_data_full(G_OBJECT(buffer), + "graptos-regex-match-bg", + g_strdup(tab && tab->win && tab->win->search_match_bg_color + ? tab->win->search_match_bg_color : ""), + g_free); + g_object_set_data_full(G_OBJECT(buffer), + "graptos-regex-match-fg", + g_strdup(tab && tab->win && tab->win->search_match_fg_color + ? tab->win->search_match_fg_color : ""), + g_free); g_object_set_data(G_OBJECT(buffer), "graptos-regex-status", status); g_signal_connect(buffer, "changed", G_CALLBACK(regex_tester_text_changed), NULL); @@ -1171,7 +1194,7 @@ static void show_regex_tester_in_hover(EditorTab *tab, gtk_list_box_insert(GTK_LIST_BOX(tab->hover_list), header, -1); GtkWidget *test_view = NULL; gtk_list_box_insert(GTK_LIST_BOX(tab->hover_list), - regex_tester_row_new(pattern, &test_view), + regex_tester_row_new(tab, pattern, &test_view), -1); (void)where; diff --git a/src/editor_tab_preview.c b/src/editor_tab_preview.c index 04dd150..5f0b27f 100644 --- a/src/editor_tab_preview.c +++ b/src/editor_tab_preview.c @@ -333,15 +333,20 @@ static void ensure_tag(GtkTextBuffer *buffer, const char *first_property, ...) { GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer); - if (gtk_text_tag_table_lookup(table, name)) return; - - GtkTextTag *tag = gtk_text_tag_new(name); + GtkTextTag *tag = gtk_text_tag_table_lookup(table, name); + gboolean created = FALSE; + if (!tag) { + tag = gtk_text_tag_new(name); + created = TRUE; + } va_list args; va_start(args, first_property); g_object_set_valist(G_OBJECT(tag), first_property, args); va_end(args); - gtk_text_tag_table_add(table, tag); - g_object_unref(tag); + if (created) { + gtk_text_tag_table_add(table, tag); + g_object_unref(tag); + } } /** @@ -352,6 +357,16 @@ static void ensure_tag(GtkTextBuffer *buffer, void preview_ensure_tags(EditorTab *tab) { if (!tab || !tab->preview_buffer) return; GtkTextBuffer *buffer = tab->preview_buffer; + const char *code_bg = tab->win && tab->win->codex_prompt_bg_color + ? tab->win->codex_prompt_bg_color : NULL; + const char *muted = tab->win && tab->win->scroll_preview_fg_color + ? tab->win->scroll_preview_fg_color : NULL; + const char *link = tab->win && tab->win->ref_popover_title_color + ? tab->win->ref_popover_title_color : NULL; + const char *math = tab->win && tab->win->ref_popover_heading_color + ? tab->win->ref_popover_heading_color : NULL; + const char *command = tab->win && tab->win->git_status_added_color + ? tab->win->git_status_added_color : NULL; ensure_tag(buffer, "preview-h1", "weight", PANGO_WEIGHT_BOLD, "scale", 1.65, "pixels-above-lines", 12, NULL); ensure_tag(buffer, "preview-h2", "weight", PANGO_WEIGHT_BOLD, @@ -368,19 +383,19 @@ void preview_ensure_tags(EditorTab *tab) { ensure_tag(buffer, "preview-italic", "style", PANGO_STYLE_ITALIC, NULL); ensure_tag(buffer, "preview-strike", "strikethrough", TRUE, NULL); ensure_tag(buffer, "preview-code", "family", "monospace", - "background", "#2A2E3D", NULL); + "background", code_bg, NULL); ensure_tag(buffer, "preview-quote", "style", PANGO_STYLE_ITALIC, - "foreground", "#8FA1B3", NULL); + "foreground", muted, NULL); ensure_tag(buffer, "preview-link", "underline", PANGO_UNDERLINE_SINGLE, - "foreground", "#61AFEF", NULL); + "foreground", link, NULL); ensure_tag(buffer, "preview-table", "family", "monospace", NULL); ensure_tag(buffer, "preview-task", "family", "monospace", "weight", PANGO_WEIGHT_BOLD, NULL); - ensure_tag(buffer, "preview-rule", "foreground", "#8FA1B3", NULL); + ensure_tag(buffer, "preview-rule", "foreground", muted, NULL); ensure_tag(buffer, "preview-math", "family", "monospace", - "foreground", "#C586C0", NULL); + "foreground", math, NULL); ensure_tag(buffer, "preview-command", "weight", PANGO_WEIGHT_BOLD, - "foreground", "#4EC9B0", NULL); + "foreground", command, NULL); } /** diff --git a/src/terminal_panel.c b/src/terminal_panel.c index 366c518..fb64109 100644 --- a/src/terminal_panel.c +++ b/src/terminal_panel.c @@ -299,8 +299,10 @@ static void terminal_panel_apply_height(TerminalPanel *panel) { } /** - * @brief Apply current editor colors and font to one terminal session. - * @details Terminal handling crosses GTK widgets, VTE child processes, and project directories. The comment keeps the lifetime rule visible so closing tabs and shell exits do not race each other. + * @brief Apply current theme colors and font to one terminal session. + * @details The terminal is painted by VTE instead of normal GTK CSS, so it + * receives the bottom-panel theme role directly rather than inheriting + * editor colors through the generated stylesheet. * @param session The session supplied by the caller. */ static void terminal_session_apply_colors(TerminalSession *session) { @@ -313,8 +315,8 @@ static void terminal_session_apply_colors(TerminalSession *session) { GdkRGBA fg; GdkRGBA bg; GdkRGBA cursor; - const char *fg_text = win->editor_fg_color ? win->editor_fg_color : "#d4d4d4"; - const char *bg_text = win->editor_bg_color ? win->editor_bg_color : "#181a1f"; + const char *fg_text = win->bottombar_fg_color ? win->bottombar_fg_color : "#d4d4d4"; + const char *bg_text = win->bottombar_bg_color ? win->bottombar_bg_color : "#111318"; const char *cursor_text = win->editor_cursor_color ? win->editor_cursor_color : fg_text; diff --git a/src/ui.h b/src/ui.h index 504cbc4..f6d2f8a 100644 --- a/src/ui.h +++ b/src/ui.h @@ -38,8 +38,11 @@ void graptos_apply_css(void); * @param sidebar_bg_color The sidebar bg color supplied by the caller. * @param tabbar_bg_color The tabbar bg color supplied by the caller. * @param tabbar_fg_color The tabbar fg color supplied by the caller. + * @param tabbar_border_color The tabbar border color supplied by the caller. * @param tab_active_bg_color The tab active bg color supplied by the caller. * @param tab_active_fg_color The tab active fg color supplied by the caller. + * @param tab_active_border_color The tab active underline color supplied by the caller. + * @param tab_tiled_indicator_color The tiled tab indicator color supplied by the caller. * @param topbar_bg_color The topbar bg color supplied by the caller. * @param topbar_fg_color The topbar fg color supplied by the caller. * @param bottombar_bg_color The bottombar bg color supplied by the caller. @@ -119,8 +122,11 @@ void graptos_apply_editor_css(const char *editor_bg_color, const char *sidebar_bg_color, const char *tabbar_bg_color, const char *tabbar_fg_color, + const char *tabbar_border_color, const char *tab_active_bg_color, const char *tab_active_fg_color, + const char *tab_active_border_color, + const char *tab_tiled_indicator_color, const char *topbar_bg_color, const char *topbar_fg_color, const char *bottombar_bg_color, @@ -188,6 +194,26 @@ void graptos_apply_editor_css(const char *editor_bg_color, const char *terminal_font, const char *code_font, gboolean use_system_interface_font); +/** + * @brief Apply theme CSS after generated theme CSS. + * @details Theme CSS is loaded at the highest application priority so local + * theme files can override both compiled structure and generated + * colour rules without rebuilding Graptoς. + * @param path The CSS file path supplied by config. + * @param enabled TRUE when theme CSS loading should be active. + */ +void graptos_apply_theme_css(const char *path, gboolean enabled); +/** + * @brief Write a complete editable theme CSS template. + * @details The template documents the stable selectors and colour names that + * users can override after compilation. Existing files are kept unless + * overwrite is TRUE. + * @param path The destination path. + * @param overwrite TRUE to replace an existing file. + * @return TRUE when the template exists after the call; otherwise FALSE. + */ +gboolean graptos_write_theme_css_template(const char *path, + gboolean overwrite); /** * @brief Graptoς button new. * @details Shared UI helpers keep Graptoς styling and GTK ownership rules in one place. The comment notes the small contract each wrapper protects for callers. diff --git a/src/ui/ui_base_css.inc.c b/src/ui/ui_base_css.inc.c index d1592be..919494d 100644 --- a/src/ui/ui_base_css.inc.c +++ b/src/ui/ui_base_css.inc.c @@ -164,14 +164,13 @@ void graptos_apply_css(void) { " padding: 0;" " margin: 0 1px 0 0;" " border: none;" - " border-right: 1px solid alpha(currentColor, 0.12);" " border-radius: 0;" " box-shadow: none;" "}" ".graptos-root notebook > header.top > tabs > tab * { border-radius: 0; box-shadow: none; }" - ".graptos-root notebook > header.top > tabs > tab:checked { border-bottom: 2px solid #8bd5ca; }" + ".graptos-root notebook > header.top > tabs > tab:checked { border-bottom: 2px solid currentColor; }" ".graptos-tab { padding: 6px 16px; min-width: 160px; }" - ".graptos-tab-tiled { box-shadow: inset 0 -2px #cba6f7; }" + ".graptos-tab-tiled { box-shadow: inset 0 -2px currentColor; }" ".graptos-tab-close {" " padding: 0 4px;" " min-width: 18px;" @@ -196,13 +195,13 @@ void graptos_apply_css(void) { ".graptos-project-icon { opacity: 0.78; font-size: 10pt; }" ".graptos-project-lock { opacity: 0.62; margin: 0 2px; }" ".graptos-git-status { font-weight: 800; min-width: 18px; opacity: 1.0; }" - ".graptos-git-status-modified { color: #f9c74f; }" - ".graptos-git-status-added { color: #57cc99; }" - ".graptos-git-status-deleted { color: #ff6b6b; }" - ".graptos-git-status-renamed { color: #4cc9f0; }" - ".graptos-git-status-conflict { color: #ff4d6d; }" - ".graptos-git-status-untracked { color: #77bdfb; }" - ".graptos-git-status-staged { color: #cba6f7; }" + ".graptos-git-status-modified," + ".graptos-git-status-added," + ".graptos-git-status-deleted," + ".graptos-git-status-renamed," + ".graptos-git-status-conflict," + ".graptos-git-status-untracked," + ".graptos-git-status-staged { color: currentColor; }" ".graptos-search-panel { border-top: 1px solid alpha(currentColor, 0.18); padding: 5px 8px; }" ".graptos-tool-panel { border-top: 1px solid alpha(currentColor, 0.18); padding: 5px 8px; min-height: 34px; }" ".graptos-tool-title { opacity: 0.72; font-size: 8pt; font-weight: 700; padding: 0 4px; }" @@ -225,13 +224,13 @@ void graptos_apply_css(void) { " border: 1px solid alpha(currentColor, 0.16);" "}" ".graptos-codex-approval {" - " border: 1px solid #d6a84b;" - " background: alpha(#d6a84b, 0.10);" + " border: 1px solid alpha(currentColor, 0.55);" + " background: alpha(currentColor, 0.10);" " padding: 8px;" "}" ".graptos-codex-review {" - " border: 1px solid alpha(#77bdfb, 0.55);" - " background: alpha(#77bdfb, 0.08);" + " border: 1px solid alpha(currentColor, 0.55);" + " background: alpha(currentColor, 0.08);" " padding: 8px;" "}" ".graptos-terminal-panel {" @@ -326,12 +325,12 @@ void graptos_apply_css(void) { ".graptos-completion-scroll { border: none; }" ".graptos-completion-list, .graptos-ref-list { border: none; border-radius: 0; }" ".graptos-completion-list row, .graptos-ref-list row { border-radius: 0; padding: 1px; }" - ".graptos-completion-list row:selected { background: #89b4fa; color: #11111b; }" + ".graptos-completion-list row:selected { background: alpha(currentColor, 0.18); color: currentColor; }" ".graptos-completion-title { font-family: monospace; font-weight: 650; }" ".graptos-completion-detail { opacity: 0.72; font-size: 8.5pt; font-family: monospace; }" ".graptos-ref-list row:hover { border: none; outline: none; box-shadow: none; }" - ".graptos-ref-heading { color: #cba6f7; font-weight: 700; }" - ".graptos-ref-title { color: #89dceb; font-family: monospace; font-weight: 600; }" + ".graptos-ref-heading { color: currentColor; font-weight: 700; }" + ".graptos-ref-title { color: currentColor; font-family: monospace; font-weight: 600; }" ".graptos-ref-kind { opacity: 0.72; font-style: italic; font-size: 8.5pt; }" ".graptos-ref-snippet { font-family: monospace; font-size: 9pt; }" ".graptos-color-label { padding: 4px; }" diff --git a/src/ui/ui_editor_css.inc.c b/src/ui/ui_editor_css.inc.c index 9790819..a34a4a0 100644 --- a/src/ui/ui_editor_css.inc.c +++ b/src/ui/ui_editor_css.inc.c @@ -93,8 +93,11 @@ static void append_font_rule(GString *css, * @param sidebar_bg_color The sidebar bg color supplied by the caller. * @param tabbar_bg_color The tabbar bg color supplied by the caller. * @param tabbar_fg_color The tabbar fg color supplied by the caller. + * @param tabbar_border_color The tabbar border color supplied by the caller. * @param tab_active_bg_color The tab active bg color supplied by the caller. * @param tab_active_fg_color The tab active fg color supplied by the caller. + * @param tab_active_border_color The tab active underline color supplied by the caller. + * @param tab_tiled_indicator_color The tiled tab indicator color supplied by the caller. * @param topbar_bg_color The topbar bg color supplied by the caller. * @param topbar_fg_color The topbar fg color supplied by the caller. * @param bottombar_bg_color The bottombar bg color supplied by the caller. @@ -174,8 +177,11 @@ void graptos_apply_editor_css(const char *editor_bg_color, const char *sidebar_bg_color, const char *tabbar_bg_color, const char *tabbar_fg_color, + const char *tabbar_border_color, const char *tab_active_bg_color, const char *tab_active_fg_color, + const char *tab_active_border_color, + const char *tab_tiled_indicator_color, const char *topbar_bg_color, const char *topbar_fg_color, const char *bottombar_bg_color, @@ -272,21 +278,30 @@ void graptos_apply_editor_css(const char *editor_bg_color, const char *effective_cursor = (editor_cursor_color && editor_cursor_color[0] == '#') ? editor_cursor_color : effective_fg; const char *effective_sidebar = - (sidebar_bg_color && sidebar_bg_color[0] == '#') ? sidebar_bg_color : effective_bg; + (sidebar_bg_color && sidebar_bg_color[0] == '#') ? sidebar_bg_color : "#111318"; const char *effective_tabbar = - (tabbar_bg_color && tabbar_bg_color[0] == '#') ? tabbar_bg_color : effective_bg; + (tabbar_bg_color && tabbar_bg_color[0] == '#') ? tabbar_bg_color : "#111318"; const char *effective_tabbar_fg = (tabbar_fg_color && tabbar_fg_color[0] == '#') ? tabbar_fg_color : effective_fg; + const char *effective_tabbar_border = + (tabbar_border_color && tabbar_border_color[0] == '#') ? + tabbar_border_color : "#00000000"; const char *effective_tab_active_bg = (tab_active_bg_color && tab_active_bg_color[0] == '#') ? tab_active_bg_color : "#20232b"; const char *effective_tab_active_fg = (tab_active_fg_color && tab_active_fg_color[0] == '#') ? tab_active_fg_color : "#ffffff"; + const char *effective_tab_active_border = + (tab_active_border_color && tab_active_border_color[0] == '#') ? + tab_active_border_color : "#89b4fa"; + const char *effective_tab_tiled_indicator = + (tab_tiled_indicator_color && tab_tiled_indicator_color[0] == '#') ? + tab_tiled_indicator_color : effective_tab_active_border; const char *effective_topbar_bg = - (topbar_bg_color && topbar_bg_color[0] == '#') ? topbar_bg_color : effective_bg; + (topbar_bg_color && topbar_bg_color[0] == '#') ? topbar_bg_color : "#111318"; const char *effective_topbar_fg = (topbar_fg_color && topbar_fg_color[0] == '#') ? topbar_fg_color : effective_fg; const char *effective_bottombar_bg = - (bottombar_bg_color && bottombar_bg_color[0] == '#') ? bottombar_bg_color : effective_bg; + (bottombar_bg_color && bottombar_bg_color[0] == '#') ? bottombar_bg_color : "#111318"; const char *effective_bottombar_fg = (bottombar_fg_color && bottombar_fg_color[0] == '#') ? bottombar_fg_color : effective_fg; const char *effective_status_error = @@ -327,12 +342,12 @@ void graptos_apply_editor_css(const char *editor_bg_color, (git_status_staged_color && git_status_staged_color[0] == '#') ? git_status_staged_color : "#cba6f7"; const char *effective_preview = (scroll_preview_bg_color && scroll_preview_bg_color[0] == '#') ? - scroll_preview_bg_color : effective_bg; + scroll_preview_bg_color : "#111318"; const char *effective_preview_fg = (scroll_preview_fg_color && scroll_preview_fg_color[0] == '#') ? scroll_preview_fg_color : "#8b949e"; const char *effective_popover = - (popover_bg_color && popover_bg_color[0] == '#') ? popover_bg_color : effective_bg; + (popover_bg_color && popover_bg_color[0] == '#') ? popover_bg_color : "#1b1f24"; const char *effective_popover_border = (popover_border_color && popover_border_color[0] == '#') ? popover_border_color : "#00000000"; @@ -420,13 +435,13 @@ void graptos_apply_editor_css(const char *editor_bg_color, (diagnostic_warning_fg_color && diagnostic_warning_fg_color[0] == '#') ? diagnostic_warning_fg_color : "#ffd166"; const char *effective_codex_preview = (codex_preview_bg_color && codex_preview_bg_color[0] == '#') - ? codex_preview_bg_color : effective_bg; + ? codex_preview_bg_color : "#1b1f24"; const char *effective_codex_preview_fg = (codex_preview_fg_color && codex_preview_fg_color[0] == '#') ? codex_preview_fg_color : effective_fg; const char *effective_codex_prompt = (codex_prompt_bg_color && codex_prompt_bg_color[0] == '#') - ? codex_prompt_bg_color : effective_bg; + ? codex_prompt_bg_color : "#111318"; const char *effective_ui_font = (ui_font && ui_font[0] != '\0') ? ui_font : NULL; const char *effective_editor_font = @@ -438,6 +453,34 @@ void graptos_apply_editor_css(const char *editor_bg_color, const char *effective_code_font = (code_font && code_font[0] != '\0') ? code_font : "monospace"; + g_string_append_printf(css, + "@define-color graptos_editor_bg %s;\n" + "@define-color graptos_editor_fg %s;\n" + "@define-color graptos_sidebar_bg %s;\n" + "@define-color graptos_tabbar_bg %s;\n" + "@define-color graptos_tabbar_border %s;\n" + "@define-color graptos_tab_active_border %s;\n" + "@define-color graptos_tab_tiled_indicator %s;\n" + "@define-color graptos_topbar_bg %s;\n" + "@define-color graptos_bottombar_bg %s;\n" + "@define-color graptos_popover_bg %s;\n" + "@define-color graptos_dialog_bg %s;\n" + "@define-color graptos_accent %s;\n" + "@define-color graptos_warning_bg %s;\n", + effective_bg, + effective_fg, + effective_sidebar, + effective_tabbar, + effective_tabbar_border, + effective_tab_active_border, + effective_tab_tiled_indicator, + effective_topbar_bg, + effective_bottombar_bg, + effective_popover, + effective_dialog_bg, + effective_completion_selection_bg, + effective_diag_warn_bg); + append_font_rule(css, ".graptos-root, .graptos-root label, .graptos-root button, " ".graptos-root entry, .graptos-root popover, " @@ -457,17 +500,9 @@ void graptos_apply_editor_css(const char *editor_bg_color, g_string_append_printf(css, "window.graptos-window, window.graptos-window > contents, " - "window.graptos-window > contents > *, " - "window.graptos-window box, window.graptos-window paned, " - "window.graptos-window stack, window.graptos-window revealer, " - "window.graptos-window scrolledwindow, window.graptos-window viewport, " ".graptos-root, .graptos-root > box, .graptos-tab-page, " ".graptos-editor-area, .graptos-editor-overlay, " - ".graptos-root paned, .graptos-root revealer, " - ".graptos-root frame, .graptos-root viewport, " - ".graptos-root scrolledwindow, .graptos-root stack, " - ".graptos-root notebook, .graptos-root notebook > stack, " - ".graptos-root notebook > stack > box { " + ".graptos-editor-content { " "background: %s; background-color: %s; color: %s; }\n", effective_bg, effective_bg, effective_fg); @@ -580,13 +615,29 @@ void graptos_apply_editor_css(const char *editor_bg_color, effective_git_untracked, effective_git_staged); g_string_append_printf(css, ".graptos-root notebook > header { background: %s; background-color: %s; color: %s; }\n" - ".graptos-root notebook > header.top > tabs > tab { background: %s; background-color: %s; color: %s; }\n" - ".graptos-root notebook > header.top > tabs > tab:checked { background: %s; background-color: %s; color: %s; }\n" + ".graptos-root notebook > header.top { border-color: %s; border-bottom-color: %s; }\n" + ".graptos-root notebook > header.top > tabs > tab { " + "background: %s; background-color: %s; color: %s; " + "border-left-color: %s; border-right-color: %s; " + "border-top-color: %s; border-bottom-color: %s; }\n" + ".graptos-root notebook > header.top > tabs > tab:checked { " + "background: %s; background-color: %s; color: %s; " + "border-left-color: %s; border-right-color: %s; " + "border-top-color: %s; border-bottom-color: %s; }\n" + ".graptos-tab-tiled { box-shadow: inset 0 -2px %s; }\n" ".graptos-tab label { color: %s; opacity: 0.68; }\n" ".graptos-root notebook > header.top > tabs > tab:checked .graptos-tab label { color: %s; opacity: 1.0; }\n", effective_tabbar, effective_tabbar, effective_tabbar_fg, + effective_tabbar_border, + effective_tab_active_border, effective_tabbar, effective_tabbar, effective_tabbar_fg, + effective_tabbar_border, effective_tabbar_border, + effective_tabbar_border, effective_tabbar_border, effective_tab_active_bg, effective_tab_active_bg, effective_tab_active_fg, + effective_tabbar_border, effective_tabbar_border, + effective_tabbar_border, + effective_tab_active_border, + effective_tab_tiled_indicator, effective_tabbar_fg, effective_tab_active_fg); g_string_append_printf(css, @@ -620,6 +671,15 @@ void graptos_apply_editor_css(const char *editor_bg_color, append_fg_rule(css, ".graptos-codex-preview text", effective_codex_preview_fg); append_bg_rule(css, ".graptos-codex-prompt", effective_codex_prompt); append_bg_rule(css, ".graptos-codex-prompt text", effective_codex_prompt); + g_string_append_printf(css, + ".graptos-codex-approval { background: %s; background-color: %s; " + "border-color: %s; }\n" + ".graptos-codex-review { background: %s; background-color: %s; " + "border-color: %s; }\n", + effective_diag_warn_bg, effective_diag_warn_bg, + effective_diag_warn_fg, + effective_search_match_bg, effective_search_match_bg, + effective_search_match_fg); g_string_append_printf(css, "popover.graptos-context-popover, popover.graptos-tools-popover, " "popover.graptos-context-popover contents, popover.graptos-tools-popover contents { " diff --git a/src/ui_css.c b/src/ui_css.c index 13c35d8..9d7fb91 100644 --- a/src/ui_css.c +++ b/src/ui_css.c @@ -17,6 +17,150 @@ * on the display and producing stale first-launch colours. */ static GtkCssProvider *dynamic_provider; +static GtkCssProvider *custom_provider; #include "ui/ui_base_css.inc.c" #include "ui/ui_editor_css.inc.c" + +/** + * @brief Return editable theme CSS template content. + * @details The template intentionally uses the public Graptoς CSS classes + * instead of generated internals, so users can keep the file across + * rebuilds while the theme editor gradually exposes more fields. + * @return Static CSS template text. + */ +static const char *graptos_theme_css_template(void) { + return + "/* GRAPTOS THEME BEGIN */\n" + "@define-color graptos_editor_bg #181a1f;\n" + "@define-color graptos_editor_fg #d4d4d4;\n" + "@define-color graptos_sidebar_bg #111318;\n" + "@define-color graptos_tabbar_bg #111318;\n" + "@define-color graptos_tabbar_border #00000000;\n" + "@define-color graptos_tab_active_bg #20232b;\n" + "@define-color graptos_tab_active_fg #ffffff;\n" + "@define-color graptos_tab_active_border #89b4fa;\n" + "@define-color graptos_tab_tiled_indicator #89b4fa;\n" + "@define-color graptos_topbar_bg #111318;\n" + "@define-color graptos_bottombar_bg #111318;\n" + "@define-color graptos_popover_bg #1b1f24;\n" + "@define-color graptos_dialog_bg #1b1f24;\n" + "@define-color graptos_accent #89b4fa;\n" + "@define-color graptos_warning_bg #5f4b24;\n\n" + ".graptos-root {\n" + " background: @graptos_editor_bg;\n" + " color: @graptos_editor_fg;\n" + "}\n\n" + ".graptos-editor, .graptos-editor text,\n" + "textview.graptos-editor, textview.graptos-editor text,\n" + "sourceview.graptos-editor, sourceview.graptos-editor text {\n" + " background: @graptos_editor_bg;\n" + " color: @graptos_editor_fg;\n" + "}\n\n" + ".graptos-project-pane, .graptos-project-tree, .graptos-project-tree row {\n" + " background: @graptos_sidebar_bg;\n" + "}\n\n" + ".graptos-root notebook > header,\n" + ".graptos-root notebook > header.top > tabs > tab {\n" + " background: @graptos_tabbar_bg;\n" + " border-left-color: @graptos_tabbar_border;\n" + " border-right-color: @graptos_tabbar_border;\n" + " border-top-color: @graptos_tabbar_border;\n" + " border-bottom-color: @graptos_tabbar_border;\n" + "}\n\n" + ".graptos-root notebook > header.top {\n" + " border-color: @graptos_tabbar_border;\n" + " border-bottom-color: @graptos_tab_active_border;\n" + "}\n" + ".graptos-root notebook > header.top > tabs > tab:checked {\n" + " background: @graptos_tab_active_bg;\n" + " color: @graptos_tab_active_fg;\n" + " border-left-color: @graptos_tabbar_border;\n" + " border-right-color: @graptos_tabbar_border;\n" + " border-top-color: @graptos_tabbar_border;\n" + " border-bottom-color: @graptos_tab_active_border;\n" + "}\n\n" + ".graptos-top { background: @graptos_topbar_bg; }\n" + ".graptos-bottom, .graptos-search-panel, .graptos-tool-panel {\n" + " background: @graptos_bottombar_bg;\n" + "}\n\n" + "popover.graptos-context-popover, popover.graptos-tools-popover,\n" + "popover.graptos-completion-popover, popover.graptos-hover-popover {\n" + " background: @graptos_popover_bg;\n" + "}\n\n" + ".graptos-dialog-root { background: @graptos_dialog_bg; }\n" + ".graptos-tab-tiled { box-shadow: inset 0 -2px @graptos_tab_tiled_indicator; }\n" + ".graptos-completion-list row:selected { background: @graptos_accent; }\n" + ".graptos-diagnostic-warning { background: @graptos_warning_bg; }\n" + "/* GRAPTOS THEME END */\n"; +} + +/** + * @brief Write an editable theme CSS template. + * @details Existing files are preserved unless overwrite is requested. The + * parent directory is created on demand so first launch can expose the + * post-compile CSS entrypoint without requiring manual setup. + * @param path The destination path. + * @param overwrite TRUE to replace an existing file. + * @return TRUE when the template exists after the call; otherwise FALSE. + */ +gboolean graptos_write_theme_css_template(const char *path, + gboolean overwrite) { + if (!path || path[0] == '\0') return FALSE; + if (!overwrite && g_file_test(path, G_FILE_TEST_IS_REGULAR)) return TRUE; + + g_autofree char *dir = g_path_get_dirname(path); + if (!dir || g_mkdir_with_parents(dir, 0700) != 0) return FALSE; + + return g_file_set_contents(path, + graptos_theme_css_template(), + -1, + NULL); +} + +/** + * @brief Remove the active theme CSS provider. + * @details Reloading themes replaces providers instead of stacking them so + * deleted or changed CSS files cannot keep stale rules alive. + * @param display The GTK display that owns CSS providers. + */ +static void graptos_remove_theme_css_provider(GdkDisplay *display) { + if (!display || !custom_provider) return; + gtk_style_context_remove_provider_for_display( + display, GTK_STYLE_PROVIDER(custom_provider)); + g_clear_object(&custom_provider); +} + +/** + * @brief Apply theme CSS after generated fallback CSS. + * @details CSS file creation belongs to config migration and the Theme dialog. + * The provider only loads an existing active theme file so missing + * paths cannot create stale or empty override files. + * @param path The CSS file path supplied by config. + * @param enabled TRUE when theme CSS loading should be active. + */ +void graptos_apply_theme_css(const char *path, gboolean enabled) { + GdkDisplay *display = gdk_display_get_default(); + if (!display) return; + + graptos_remove_theme_css_provider(display); + if (!enabled || !path || path[0] == '\0') return; + if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) return; + + g_autofree char *css = NULL; + gsize length = 0u; + g_autoptr(GError) error = NULL; + if (!g_file_get_contents(path, &css, &length, &error) || !css) { + g_warning("Could not load Graptoς theme CSS %s: %s", + path, + error ? error->message : "unknown error"); + return; + } + + custom_provider = gtk_css_provider_new(); + gtk_css_provider_load_from_string(custom_provider, css); + gtk_style_context_add_provider_for_display( + display, + GTK_STYLE_PROVIDER(custom_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 20u); +} diff --git a/syntax/c.yaml b/syntax/c.yaml index 113cbe6..03bf12a 100644 --- a/syntax/c.yaml +++ b/syntax/c.yaml @@ -8,7 +8,7 @@ line_comment: // auto_indent: true indent_openers: ["{"] indent_closers: ["}"] -close_pairs: ["(=>)", "{=>}", "[=>]", "<=>>", '"=>"'] +close_pairs: ["(=>)", "{=>}", "[=>]"] line_close_pairs: ["#include <=>>"] statement_required_enders: [";"] statement_exempt_prefixes: ["#", "//", "/*", "*", "}", "{", "else", "case ", "default:", "if ", "if(", "for ", "for(", "while ", "while(", "switch ", "switch(", "do", "struct ", "enum ", "union ", "typedef "] @@ -599,7 +599,7 @@ formatting: max_column: 100 wrap_after_comma: true case_indent: 0 - preserve_blank_lines: false + preserve_blank_lines: true max_blank_lines: 0 collapse_empty_blocks: true rules: diff --git a/syntax/zig.yaml b/syntax/zig.yaml index 3f92b3a..00991c2 100644 --- a/syntax/zig.yaml +++ b/syntax/zig.yaml @@ -89,7 +89,7 @@ formatting: max_column: 100 wrap_after_comma: true case_indent: 0 - preserve_blank_lines: false + preserve_blank_lines: true max_blank_lines: 0 collapse_empty_blocks: true