diff --git a/colors.v b/colors.v index d5c3d441..17a4dc62 100644 --- a/colors.v +++ b/colors.v @@ -5,26 +5,26 @@ import tauraamui.bobatea as tea // TODO(tauraamui): consolidate all used colors into a core palette/swatch set // and alias reference off of that only in the places/models they are used with // alternative names, instead of here. This file is getting messy and out of hand. -pub const matte_black_bg_color = tea.Color{ 20, 20, 20 } +pub const matte_black_bg_color = tea.Color{20, 20, 20} -pub const petal_pink_color = tea.Color{ 245, 191, 243 } -pub const petal_green_color = tea.Color{ 97, 242, 136 } -pub const petal_red_color = tea.Color{ 245, 42, 42 } +pub const petal_pink_color = tea.Color{245, 191, 243} +pub const petal_green_color = tea.Color{97, 242, 136} +pub const petal_red_color = tea.Color{245, 42, 42} -pub const status_green = tea.Color{ 145, 237, 145 } -pub const status_orange = tea.Color{ 237, 207, 123 } -pub const status_lilac = tea.Color{ 194, 110, 230 } -pub const status_dark_lilac = tea.Color{ 154, 119, 209 } -pub const status_cyan = tea.Color{ 138, 222, 237 } -pub const status_purple = tea.Color{ 130, 144, 250 } +pub const status_green = tea.Color{145, 237, 145} +pub const status_orange = tea.Color{237, 207, 123} +pub const status_lilac = tea.Color{194, 110, 230} +pub const status_dark_lilac = tea.Color{154, 119, 209} +pub const status_cyan = tea.Color{138, 222, 237} +pub const status_purple = tea.Color{130, 144, 250} pub const error_color = petal_red_color // -pub const matte_black_fg_color = tea.Color.ansi(232) -pub const matte_white_fg_color = tea.Color{ 230, 230, 230 } -pub const bright_off_white_fg_color = tea.Color{ 255, 255, 255 } +pub const matte_black_fg_color = tea.Color.ansi(232) +pub const matte_white_fg_color = tea.Color{230, 230, 230} +pub const bright_off_white_fg_color = tea.Color{255, 255, 255} pub const subtle_text_fg_color = tea.Color.ansi(249) pub const help_fg_color = tea.Color.ansi(241) @@ -33,10 +33,10 @@ pub const selected_highlight_bg_color = tea.Color.ansi(239) pub const subtle_border_fg_color = petal_pink_color -pub const status_bar_bg_color = tea.Color.ansi(234) -pub const status_file_name_bg_color = tea.Color{ 86, 86, 86 } -pub const status_branch_name_bg_color = tea.Color{ 154, 119, 209 } -pub const status_cursor_pos_bg_color = tea.Color{ 245, 42, 42 } +pub const status_bar_bg_color = tea.Color.ansi(234) +pub const status_file_name_bg_color = tea.Color{86, 86, 86} +pub const status_branch_name_bg_color = tea.Color{154, 119, 209} +pub const status_cursor_pos_bg_color = tea.Color{245, 42, 42} pub fn fg_color(background_color tea.Color) tea.Color { s_r := f32(background_color.r) / 255 @@ -47,4 +47,3 @@ pub fn fg_color(background_color tea.Color) tea.Color { return if luminance > 0.5 { matte_black_fg_color } else { matte_white_fg_color } } - diff --git a/colors_test.v b/colors_test.v index 1b93179a..522928bf 100644 --- a/colors_test.v +++ b/colors_test.v @@ -3,7 +3,6 @@ module palette import tauraamui.bobatea as tea fn test_fg_color_from_shades_of_bg() { - assert fg_color(tea.Color{ 10, 10, 10 }) == matte_white_fg_color - assert fg_color(tea.Color{ 200, 200, 200 }) == matte_black_fg_color + assert fg_color(tea.Color{10, 10, 10}) == matte_white_fg_color + assert fg_color(tea.Color{200, 200, 200}) == matte_black_fg_color } - diff --git a/config.v b/config.v index a72d0a06..33fcceeb 100644 --- a/config.v +++ b/config.v @@ -5,7 +5,7 @@ import theme pub const light_theme_name = theme.light_theme_name pub const dark_theme_name = theme.dark_theme_name -pub struct Config{ +pub struct Config { pub: theme theme.Theme leader_key string @@ -14,7 +14,7 @@ pub: @[params] pub struct ConfigOptions { pub: - load_from_path ?string = "~/.config/petal/petal.cfg" + load_from_path ?string = '~/.config/petal/petal.cfg' } pub fn Config.new(opts ConfigOptions) Config { @@ -25,15 +25,14 @@ pub fn Config.new(opts ConfigOptions) Config { } return Config{ - theme: theme.dark_theme - leader_key: ";" + theme: theme.dark_theme + leader_key: ';' } } pub fn (c Config) set_theme(name string) Config { return Config{ ...c - theme: if name == "dark" { theme.dark_theme } else { theme.light_theme } + theme: if name == 'dark' { theme.dark_theme } else { theme.light_theme } } } - diff --git a/debug_screen.v b/debug_screen.v index 5e7e5e7d..5d17e2df 100644 --- a/debug_screen.v +++ b/debug_screen.v @@ -9,7 +9,7 @@ import palette interface DebuggableModel { tea.Model Debuggable - width() int + width() int height() int } @@ -58,18 +58,18 @@ enum LogLevel { fn (l LogLevel) str() string { return match l { - .debug { "DEBU" } - .info { "INFO" } - .warn { "WARN" } - .error { "ERRO" } + .debug { 'DEBU' } + .info { 'INFO' } + .warn { 'WARN' } + .error { 'ERRO' } } } fn (l LogLevel) fg() tea.Color { return match l { .debug { tea.Color.ansi(62) } - .info { tea.Color.ansi(183) } - .warn { tea.Color.ansi(220) } + .info { tea.Color.ansi(183) } + .warn { tea.Color.ansi(220) } .error { tea.Color.ansi(162) } } } @@ -80,7 +80,7 @@ struct LogMsg { } fn log(message string, level LogLevel) tea.Msg { - return LogMsg{ level, message } + return LogMsg{level, message} } fn debug_log(message string) tea.Cmd { @@ -253,11 +253,11 @@ fn render_logs(mut ctx tea.Context, x int, y int, logs []LogMsg) { ctx.set_color(l.level.fg()) mut label_x_offset := 0 - ctx.draw_text(x + label_x_offset, y + i, "[") + ctx.draw_text(x + label_x_offset, y + i, '[') label_x_offset += 1 ctx.draw_text(x + label_x_offset, y + i, level_label) label_x_offset += tea.visible_len(level_label) - ctx.draw_text(x + label_x_offset, y + i, "]") + ctx.draw_text(x + label_x_offset, y + i, ']') label_x_offset += 1 ctx.reset_color() label_x_offset += 1 // single space padding @@ -301,9 +301,13 @@ fn (m DebugScreenModel) debug_data() DebugData { } } -fn (m DebugScreenModel) width() int { return m.window_width } +fn (m DebugScreenModel) width() int { + return m.window_width +} -fn (m DebugScreenModel) height() int { return m.window_height } +fn (m DebugScreenModel) height() int { + return m.window_height +} fn (m DebugScreenModel) clone() tea.Model { return DebugScreenModel{ diff --git a/editor.v b/editor.v index 6c398492..23728675 100644 --- a/editor.v +++ b/editor.v @@ -11,26 +11,46 @@ struct ModelCursorPos { fn (c ModelCursorPos) up() ModelCursorPos { yy := c.y - 1 - if yy < 0 { return c } - return ModelCursorPos{ y: yy, x: 0 } + if yy < 0 { + return c + } + return ModelCursorPos{ + y: yy + x: 0 + } } fn (c ModelCursorPos) down(max int) ModelCursorPos { yy := c.y + 1 - if yy >= max { return c } - return ModelCursorPos{ y: yy, x: 0 } + if yy >= max { + return c + } + return ModelCursorPos{ + y: yy + x: 0 + } } fn (c ModelCursorPos) left() ModelCursorPos { xx := c.x - 1 - if xx < 0 { return c } - return ModelCursorPos{ y: c.y, x: xx } + if xx < 0 { + return c + } + return ModelCursorPos{ + y: c.y + x: xx + } } fn (c ModelCursorPos) right(max int) ModelCursorPos { yy := c.y + 1 - if yy >= max { return c } - return ModelCursorPos{ y: yy, x: 0 } + if yy >= max { + return c + } + return ModelCursorPos{ + y: yy + x: 0 + } } struct EditorData { @@ -60,7 +80,7 @@ struct OpenEditorMsg { fn open_editor(file_path string) tea.Cmd { return fn [file_path] () tea.Msg { - return OpenEditorMsg{ file_path } + return OpenEditorMsg{file_path} } } @@ -68,8 +88,8 @@ struct QueryEditorDataMsg {} fn query_editor_data(id int) tea.Cmd { return fn [id] () tea.Msg { - return EditorModelMsg { - id: id + return EditorModelMsg{ + id: id msg: QueryEditorDataMsg{} } } @@ -81,16 +101,20 @@ struct EditorDataResultMsg { fn editor_data(data EditorData) tea.Cmd { return fn [data] () tea.Msg { - return EditorDataResultMsg{ data } + return EditorDataResultMsg{data} } } fn EditorModel.new(id int, file_path string) EditorModel { assert file_path.len != 0 return EditorModel{ - id: id, - file_path: file_path, - lines: if content := os.read_lines(file_path) { content } else { []string{ len: 150, init: "This is a line of random text" } } + id: id + file_path: file_path + lines: if content := os.read_lines(file_path) { + content + } else { + []string{len: 150, init: 'This is a line of random text'} + } } } @@ -121,7 +145,6 @@ fn move_cursor_up() tea.Msg { return EditorCursorUpMsg{} } - fn (mut m EditorModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { mut cmds := []tea.Cmd{} @@ -129,14 +152,13 @@ fn (mut m EditorModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { match msg.k_type { .runes { match msg.string() { - "j" { + 'j' { cmds << move_cursor_down return m.clone(), tea.batch_array(cmds) } - "k" { + 'k' { cmds << move_cursor_up return m.clone(), tea.batch_array(cmds) - } else {} } @@ -147,7 +169,7 @@ fn (mut m EditorModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { match msg { tea.ResizedMsg { - m.width = msg.window_width + m.width = msg.window_width m.height = msg.window_height } EditorModelMsg { @@ -196,25 +218,30 @@ const active_editor_border_color = palette.petal_pink_color const inactive_editor_border_color = palette.status_dark_lilac fn (m EditorModel) view(mut ctx tea.Context) { - ctx.set_clip_area(tea.ClipArea{ 0, 0, m.width, m.height }) + ctx.set_clip_area(tea.ClipArea{0, 0, m.width, m.height}) defer { ctx.clear_clip_area() } if m.show_border { - border_color := if m.focused { active_editor_border_color } else { inactive_editor_border_color } + border_color := if m.focused { + active_editor_border_color + } else { + inactive_editor_border_color + } ctx.set_color(border_color) - for y in 0..m.height { + for y in 0 .. m.height { ctx.draw_text(0, y, '│') } ctx.reset_color() + ctx.push_offset(tea.Offset{ x: 1 }) } - ctx.push_offset(tea.Offset{ x: 1 }) - for y, l in m.lines { ctx.draw_text(0, y, l.replace('\t', ' ')) } - if m.focused { m.render_cursor(mut ctx) } + if m.focused { + m.render_cursor(mut ctx) + } } fn (m EditorModel) render_cursor(mut ctx tea.Context) { @@ -228,8 +255,8 @@ fn (m EditorModel) debug_data() DebugData { return DebugData{ name: 'active editor data' data: { - 'id': '${m.id}' - 'file path': m.file_path + 'id': '${m.id}' + 'file path': m.file_path 'cursor_row': '${m.cursor_pos.y}' 'cursor_col': '${m.cursor_pos.x}' } @@ -245,9 +272,13 @@ fn (m EditorModel) data() EditorData { } } -fn (m EditorModel) width() int { return m.width } +fn (m EditorModel) width() int { + return m.width +} -fn (m EditorModel) height() int { return m.height } +fn (m EditorModel) height() int { + return m.height +} fn (m EditorModel) clone() tea.Model { assert m.file_path.len != 0 @@ -255,4 +286,3 @@ fn (m EditorModel) clone() tea.Model { ...m } } - diff --git a/editor_workspace.v b/editor_workspace.v index a0b28f30..93abcf0c 100644 --- a/editor_workspace.v +++ b/editor_workspace.v @@ -5,6 +5,7 @@ import time import math import tauraamui.bobatea as tea import boba +import theme import palette import glyphs @@ -13,15 +14,16 @@ struct EditorWorkspaceModel { // NOTE(tauraamui): forced mode to be immutable, this ensures we cannot randomly // accidentally set the mode state without accounting for necessary checks and state changes, // the only way we can change the mode is by exiting the current scope with a command to do so - mode Mode + mode Mode + theme theme.Theme mut: - tmux_wrapped bool - dialog_model ?DebuggableModel + tmux_wrapped bool + dialog_model ?DebuggableModel - active_editor_id int + active_editor_id int - split_tree boba.SplitTree - editors map[int]DebuggableModel + split_tree boba.SplitTree + editors map[int]DebuggableModel active_editor_data ?EditorData branch_name string @@ -51,15 +53,16 @@ fn open_editor_workspace(initial_file_path string) tea.Cmd { } } -fn EditorWorkspaceModel.new(initial_file_path string) EditorWorkspaceModel { +fn EditorWorkspaceModel.new(ttheme theme.Theme, initial_file_path string) EditorWorkspaceModel { return EditorWorkspaceModel{ + theme: ttheme initial_file_path: initial_file_path - split_tree: boba.SplitTree.new() + split_tree: boba.SplitTree.new() } } fn (mut m EditorWorkspaceModel) init() ?tea.Cmd { - m.input_field = boba.InputField.new_with_prefix(":", 0) + m.input_field = boba.InputField.new_with_prefix(':', 0) return tea.batch(open_editor(m.initial_file_path), check_if_tmux_wrapped) } @@ -69,7 +72,7 @@ struct SwitchModeMsg { fn switch_mode(mode Mode) tea.Cmd { return fn [mode] () tea.Msg { - return SwitchModeMsg{ mode } + return SwitchModeMsg{mode} } } @@ -79,14 +82,14 @@ struct CommandMsg { fn run_command(command string) tea.Cmd { return fn [command] () tea.Msg { - return CommandMsg{ command } + return CommandMsg{command} } } fn focus_editor(editor_id int) tea.Cmd { return fn [editor_id] () tea.Msg { return EditorModelMsg{ - id: editor_id + id: editor_id msg: tea.FocusedMsg{} } } @@ -95,7 +98,7 @@ fn focus_editor(editor_id int) tea.Cmd { fn unfocus_editor(editor_id int) tea.Cmd { return fn [editor_id] () tea.Msg { return EditorModelMsg{ - id: editor_id + id: editor_id msg: tea.BlurredMsg{} } } @@ -125,7 +128,7 @@ struct DisplayErrorMsg { fn display_error(error string) tea.Cmd { return fn [error] () tea.Msg { - return DisplayErrorMsg { error } + return DisplayErrorMsg{error} } } @@ -163,7 +166,7 @@ fn pwd_git_branch_name(branch_name string) tea.Cmd { fn resolve_git_branch_name(execute fn (cmd string) os.Result) string { $if darwin { - return "(not supported on macos)" + return '(not supported on macos)' } prefix := '\uE0A0' wt := spawn currently_in_worktree(execute) @@ -221,13 +224,11 @@ fn (mut m EditorWorkspaceModel) update_dialog(msg tea.Msg) (?tea.Model, ?tea.Cmd } if mut open_model := m.dialog_model { - intercepted_msg := if msg is tea.ResizedMsg && mut open_model is FilePickerModel { tea.Msg( - // force forward a 80% of the actual window size down to moddal model - tea.ResizedMsg{ - window_width: int(f64(msg.window_width) * 0.8) + // force forward a 80% of the actual window size down to moddal model + intercepted_msg := if msg is tea.ResizedMsg && mut open_model is FilePickerModel { tea.Msg(tea.ResizedMsg{ + window_width: int(f64(msg.window_width) * 0.8) window_height: int(f64(msg.window_height) * 0.8) - } - ) } else { msg } + }) } else { msg } d, cmd := open_model.update(intercepted_msg) if d is DebuggableModel { @@ -263,7 +264,7 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { match m.leader_suffix { 'ff' { cmds << switch_mode(.normal) - cmds << open_file_picker + cmds << open_file_picker(m.theme) } else {} } @@ -303,16 +304,16 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { match msg.k_type { .special { match msg.string() { - "escape" { + 'escape' { cmds << hide_error } - "ctrl+b" { + 'ctrl+b' { cmds << switch_mode(.navigation) } - "ctrl+w+h" { + 'ctrl+w+h' { cmds << switch_active_split(.left) } - "ctrl+w+l" { + 'ctrl+w+l' { cmds << switch_active_split(.right) } else {} @@ -364,8 +365,7 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { cmds << query_pwd_git_branch } CheckIfTMUXWrappedMsg { - m.tmux_wrapped = os.getenv("TMUX").len > 0 - assert m.tmux_wrapped + m.tmux_wrapped = os.getenv('TMUX').len > 0 } OpenDialogMsg { mut d_model := msg.model @@ -398,17 +398,13 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { cmds << u_cmd } - cmds << tea.sequence( - focus_editor(editor_id), - toggle_editor_show_border(editor_id, false), - query_editor_data(editor_id), - query_pwd_git_branch - ) - cmds << debug_log("opened file ${msg.file_path} into model of id ${editor_id}") + cmds << tea.sequence(focus_editor(editor_id), toggle_editor_show_border(editor_id, + false), query_editor_data(editor_id), query_pwd_git_branch) + cmds << debug_log('opened file ${msg.file_path} into model of id ${editor_id}') } VerticalSplitMsg { if info := m.split_tree.get_active_editor() { - old_id := info.id // get the old ID before inserting + old_id := info.id // get the old ID before inserting new_id := m.next_editor_id() mut new_editor := EditorModel.new(new_id, info.file_path) if init_cmd := new_editor.init() { @@ -421,14 +417,9 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { // sync active_editor_id with split_tree m.active_editor_id = m.split_tree.active_editor_id - cmds << tea.sequence( - unfocus_editor(old_id), - toggle_editor_show_border(m.split_tree.get_leftmost_id(), false), - focus_editor(new_id), - query_editor_data(new_id), - query_pwd_git_branch, - tea.emit_resize - ) + cmds << tea.sequence(unfocus_editor(old_id), toggle_editor_show_border(m.split_tree.get_leftmost_id(), + false), focus_editor(new_id), query_editor_data(new_id), query_pwd_git_branch, + tea.emit_resize) } } CloseActiveSplitMsg { @@ -441,13 +432,9 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { cmds << tea.quit } else { // focus the new active editor - cmds << tea.sequence( - focus_editor(m.active_editor_id), - toggle_editor_show_border(m.split_tree.get_leftmost_id(), false), - query_editor_data(m.active_editor_id), - query_pwd_git_branch, - tea.emit_resize - ) + cmds << tea.sequence(focus_editor(m.active_editor_id), toggle_editor_show_border(m.split_tree.get_leftmost_id(), + false), query_editor_data(m.active_editor_id), query_pwd_git_branch, + tea.emit_resize) } } } @@ -465,12 +452,8 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { new_id := m.split_tree.active_editor_id m.active_editor_id = new_id - cmds << tea.sequence( - unfocus_editor(old_id), - focus_editor(new_id), - query_editor_data(new_id), - query_pwd_git_branch - ) + cmds << tea.sequence(unfocus_editor(old_id), focus_editor(new_id), + query_editor_data(new_id), query_pwd_git_branch) } } else { if m.tmux_wrapped { @@ -489,12 +472,8 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { new_id := m.split_tree.active_editor_id m.active_editor_id = new_id - cmds << tea.sequence( - unfocus_editor(old_id), - focus_editor(new_id), - query_editor_data(new_id), - query_pwd_git_branch - ) + cmds << tea.sequence(unfocus_editor(old_id), focus_editor(new_id), + query_editor_data(new_id), query_pwd_git_branch) } } else { if m.tmux_wrapped { @@ -512,11 +491,11 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { } CommandMsg { match msg.command { - "q" { cmds << close_active_split } - "qa" { cmds << tea.quit } - "debug" { cmds << toggle_debug_screen } - "version" { cmds << open_version_dialog } - "vs" { cmds << split_vertically } + 'q' { cmds << close_active_split } + 'qa' { cmds << tea.quit } + 'debug' { cmds << toggle_debug_screen } + 'version' { cmds << open_version_dialog(m.theme) } + 'vs' { cmds << split_vertically } else { cmds << raise_error("unknown command '${msg.command}'") } } } @@ -572,17 +551,17 @@ fn (mut m EditorWorkspaceModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { fn (m EditorWorkspaceModel) view(mut ctx tea.Context) { editor_area_height := ctx.window_height() - 2 - ctx.set_clip_area(tea.ClipArea{ 0, 0, ctx.window_width(), editor_area_height }) + ctx.set_clip_area(tea.ClipArea{0, 0, ctx.window_width(), editor_area_height}) layout := m.split_tree.get_layout(ctx.window_width(), editor_area_height) for rect in layout { if mut editor := m.editors[rect.editor_id] { // set clip area for this specific split - ctx.set_clip_area(tea.ClipArea{ rect.x, rect.y, rect.width, rect.height }) + ctx.set_clip_area(tea.ClipArea{rect.x, rect.y, rect.width, rect.height}) offset_id := ctx.push_offset(tea.Offset{ x: rect.x, y: rect.y }) resized, _ := editor.update(tea.ResizedMsg{ - window_width: rect.width + window_width: rect.width window_height: rect.height }) if resized is DebuggableModel { @@ -591,19 +570,17 @@ fn (m EditorWorkspaceModel) view(mut ctx tea.Context) { } ctx.clear_offsets_from(offset_id) - ctx.clear_clip_area() // clear after each split + ctx.clear_clip_area() // clear after each split } } m.render_status_bar(mut ctx) if mut open_model := m.dialog_model { - id := ctx.push_offset( - tea.Offset{ - x: int(f64(ctx.window_width() / 2)) - int(f64(open_model.width() / 2)) - y: int (f64(ctx.window_height() / 2)) - int(f64(open_model.height() / 2)) - } - ) + id := ctx.push_offset(tea.Offset{ + x: int(f64(ctx.window_width() / 2)) - int(f64(open_model.width() / 2)) + y: int(f64(ctx.window_height() / 2)) - int(f64(open_model.height() / 2)) + }) defer { ctx.clear_offsets_from(id) } open_model.view(mut ctx) @@ -611,7 +588,7 @@ fn (m EditorWorkspaceModel) view(mut ctx tea.Context) { } fn (m EditorWorkspaceModel) render_status_bar(mut ctx tea.Context) { - ctx.set_bg_color(palette.status_bar_bg_color) + ctx.set_bg_color(m.theme.status_bar_spacer) ctx.draw_rect(0, ctx.window_height() - 2, ctx.window_width(), 1) ctx.reset_bg_color() @@ -623,64 +600,67 @@ fn (m EditorWorkspaceModel) render_status_blocks(mut ctx tea.Context) { status_bar_offset := ctx.push_offset(tea.Offset{ y: ctx.window_height() - 2 }) defer { ctx.clear_offsets_from(status_bar_offset) } - ctx.set_color(m.mode.color()) + mode_color := m.mode.color(m.theme) + ctx.set_color(mode_color) ctx.draw_text(0, 0, '${glyphs.left_rounded}${glyphs.block}') ctx.reset_color() blocks_offset := ctx.push_offset(tea.Offset{ x: 2 }) mode_label := m.mode.str() ctx.set_color(palette.matte_black_fg_color) - ctx.set_bg_color(m.mode.color()) + ctx.set_bg_color(mode_color) ctx.draw_text(0, 0, mode_label) ctx.reset_bg_color() ctx.reset_color() ctx.push_offset(tea.Offset{ x: tea.visible_len(mode_label) }) - ctx.set_color(m.mode.color()) + ctx.set_color(mode_color) ctx.draw_text(0, 0, '${glyphs.block}${glyphs.slant_right_flat_bottom}') ctx.reset_color() ctx.push_offset(tea.Offset{ x: 2 }) - ctx.set_color(palette.status_file_name_bg_color) + file_name_bg_color := m.theme.status_file_name + ctx.set_color(file_name_bg_color) ctx.draw_text(0, 0, '${glyphs.slant_left_flat_top}${glyphs.block}') ctx.reset_color() ctx.push_offset(tea.Offset{ x: 2 }) file_name_label := m.active_file_name() - ctx.set_color(palette.fg_color(palette.status_file_name_bg_color)) - ctx.set_bg_color(palette.status_file_name_bg_color) + ctx.set_color(palette.fg_color(file_name_bg_color)) + ctx.set_bg_color(file_name_bg_color) ctx.draw_text(0, 0, file_name_label) ctx.reset_bg_color() ctx.reset_color() ctx.push_offset(tea.Offset{ x: tea.visible_len(file_name_label) }) - ctx.set_color(palette.status_file_name_bg_color) + ctx.set_color(file_name_bg_color) ctx.draw_text(0, 0, '${glyphs.block}${glyphs.slant_right_flat_bottom}') ctx.reset_color() ctx.push_offset(tea.Offset{ x: 2 }) - ctx.set_color(palette.status_branch_name_bg_color) + branch_name_bg_color := m.theme.status_branch_name + ctx.set_color(branch_name_bg_color) ctx.draw_text(0, 0, '${glyphs.slant_left_flat_top}${glyphs.block}') ctx.reset_color() ctx.push_offset(tea.Offset{ x: 2 }) branch_name_label := m.active_branch_name() - ctx.set_color(palette.matte_white_fg_color) - ctx.set_bg_color(palette.status_branch_name_bg_color) + ctx.set_color(palette.fg_color(branch_name_bg_color)) + ctx.set_bg_color(branch_name_bg_color) ctx.draw_text(0, 0, branch_name_label) ctx.reset_bg_color() ctx.reset_color() ctx.push_offset(tea.Offset{ x: tea.visible_len(branch_name_label) }) - ctx.set_color(palette.status_branch_name_bg_color) + ctx.set_color(branch_name_bg_color) ctx.draw_text(-1, 0, '${glyphs.block}${glyphs.slant_right_flat_bottom}') ctx.reset_color() // status bar spacer left end cap - ctx.set_color(palette.status_bar_bg_color) + ctx.set_color(m.theme.status_bar_spacer) ctx.draw_text(1, 0, glyphs.slant_left_flat_top) ctx.reset_color() // @@ -692,7 +672,7 @@ fn (m EditorWorkspaceModel) render_status_blocks(mut ctx tea.Context) { ctx.push_offset(tea.Offset{ x: cursor_pos_segment_start }) // status bar spacer right end cap - ctx.set_color(palette.status_bar_bg_color) + ctx.set_color(m.theme.status_bar_spacer) ctx.draw_text(-1, 0, glyphs.slant_right_flat_top) ctx.reset_color() // @@ -764,14 +744,18 @@ fn (m EditorWorkspaceModel) debug_data() DebugData { return DebugData{ name: 'editor_workspace data' data: { - 'initial file path': m.initial_file_path + 'initial file path': m.initial_file_path } } } -fn (m EditorWorkspaceModel) width() int { return 0 } +fn (m EditorWorkspaceModel) width() int { + return 0 +} -fn (m EditorWorkspaceModel) height() int { return 0 } +fn (m EditorWorkspaceModel) height() int { + return 0 +} fn (mut m EditorWorkspaceModel) clone() tea.Model { return EditorWorkspaceModel{ diff --git a/file_picker.v b/file_picker.v index a2c3abd1..1783c092 100644 --- a/file_picker.v +++ b/file_picker.v @@ -6,9 +6,11 @@ import strings import lib.files import tauraamui.bobatea as tea import palette +import theme import boba struct FilePickerModel { + theme theme.Theme mut: width int height int @@ -38,10 +40,13 @@ pub: pub struct CloseDialogMsg {} -pub fn open_file_picker() tea.Msg { - return OpenDialogMsg{ - model: FilePickerModel{ - finder: files.new_finder() +pub fn open_file_picker(ttheme theme.Theme) tea.Cmd { + return fn [ttheme] () tea.Msg { + return OpenDialogMsg{ + model: FilePickerModel{ + theme: ttheme + finder: files.new_finder() + } } } } @@ -52,7 +57,7 @@ pub fn close_file_picker() tea.Msg { pub fn (mut m FilePickerModel) init() ?tea.Cmd { m.loading = true - m.input_field = boba.BorderedInputField.new() + m.input_field = boba.BorderedInputField.new(m.theme.petal_pink) m.input_field.focus() mut cmds := []tea.Cmd{} if input_init_cmd := m.input_field.init() { @@ -171,7 +176,7 @@ fn (mut m FilePickerModel) on_cancel() (tea.Model, ?tea.Cmd) { return m.clone(), cmd } -const filter_trigger_special_keys = ["backspace", "delete"] +const filter_trigger_special_keys = ['backspace', 'delete'] fn (mut m FilePickerModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { mut cmds := []tea.Cmd{} @@ -283,15 +288,27 @@ fn calculate_cursor_color(blink_frame int) tea.Color { return tea.Color.ansi(color_value) } -fn render_file_path_line(mut ctx tea.Context, file_path string, width int, height int, is_selected bool) { +@[params] +struct RenderFilePathLineParams { + file_path string + width int + height int + is_selected bool + selection_bg_color tea.Color +} + +fn render_file_path_line(mut ctx tea.Context, opts RenderFilePathLineParams) { mut prefix := ' ' - if is_selected { + if opts.is_selected { prefix = '» ' - ctx.set_bg_color(palette.selected_highlight_bg_color) + selected_path_highlight_bg_color := opts.selection_bg_color + ctx.set_color(palette.fg_color(selected_path_highlight_bg_color)) + ctx.set_bg_color(selected_path_highlight_bg_color) } - ctx.draw_rect(0, height - 3, width - 2, 1) - ctx.draw_text(0, height - 3, prefix + file_path.replace(os.getwd(), '.')) - if is_selected { + ctx.draw_rect(0, opts.height - 3, opts.width - 2, 1) + ctx.draw_text(0, opts.height - 3, prefix + opts.file_path.replace(os.getwd(), '.')) + if opts.is_selected { + ctx.reset_color() ctx.reset_bg_color() } ctx.push_offset(tea.Offset{ y: -1 }) @@ -303,12 +320,8 @@ fn (m FilePickerModel) max_visible_items() int { return if max_height > 0 { max_height } else { 0 } } -const subtle_bordered_layout = tea.new_layout() - .border(.normal) - .border_color(palette.subtle_border_fg_color) - -fn (m FilePickerModel) render_file_results_pane(mut r_ctx tea.Context, width int, height int) { - subtle_bordered_layout.size(width, height).render(mut r_ctx, fn [m, width, height] (mut ctx tea.Context) { +fn (m FilePickerModel) render_file_results_pane(mut r_ctx tea.Context, width int, height int, border_color tea.Color) { + tea.new_layout().border(.normal).border_color(border_color).size(width, height).render(mut r_ctx, fn [m, width, height] (mut ctx tea.Context) { max_width := width - 2 max_height := height - 2 ctx.set_clip_area(tea.ClipArea{0, 0, max_width - 1, max_height}) @@ -316,7 +329,7 @@ fn (m FilePickerModel) render_file_results_pane(mut r_ctx tea.Context, width int ctx.draw_rect(0, 0, max_width, max_height) if m.loading { - ctx.set_color(palette.subtle_text_fg_color) + ctx.set_color(m.theme.subtle_light_grey) loading_label := 'Loading files…' ctx.draw_text((width / 2) - tea.visible_len(loading_label) / 2, height / 2, loading_label) @@ -328,7 +341,13 @@ fn (m FilePickerModel) render_file_results_pane(mut r_ctx tea.Context, width int list_offset_id := ctx.push_offset(tea.Offset{}) for i, file_path in clamp_files_list_to_scrolled(m.start_index, max_items, m.filtered_files) { is_selected := (i + m.start_index) == m.selected_index - render_file_path_line(mut ctx, file_path, width, height, is_selected) + render_file_path_line(mut ctx, + file_path: file_path + width: width + height: height + is_selected: is_selected + selection_bg_color: m.theme.highlight_bg_color + ) } ctx.clear_offsets_from(list_offset_id) }) @@ -361,12 +380,14 @@ fn clamp_files_list_to_scrolled(start int, max_items int, initial_files_list []s } fn (m FilePickerModel) view(mut ctx tea.Context) { - if m.width == 0 || m.height == 0 { return } + if m.width == 0 || m.height == 0 { + return + } // wipe existing rendered cells "behind" the modal ctx.draw_rect(0, 0, m.width, m.height) max_results_height := m.height - 3 - m.render_file_results_pane(mut ctx, m.width, max_results_height) + m.render_file_results_pane(mut ctx, m.width, max_results_height, m.theme.petal_pink) ctx.push_offset(tea.Offset{ y: max_results_height }) m.input_field.view(mut ctx) @@ -397,9 +418,13 @@ fn (m FilePickerModel) debug_data() DebugData { } } -fn (m FilePickerModel) width() int { return m.width } +fn (m FilePickerModel) width() int { + return m.width +} -fn (m FilePickerModel) height() int { return m.height } +fn (m FilePickerModel) height() int { + return m.height +} fn (m FilePickerModel) clone() tea.Model { return FilePickerModel{ diff --git a/file_picker_test.v b/file_picker_test.v index 299158fb..ca2ae4b4 100644 --- a/file_picker_test.v +++ b/file_picker_test.v @@ -2,6 +2,7 @@ module main import tauraamui.bobatea.lib.draw import tauraamui.bobatea as tea +import theme import lib.files struct MockFilesFinder { @@ -20,6 +21,7 @@ fn (mut f MockFilesFinder) search(root string) { fn test_file_list_loads_files() { mut fp := FilePickerModel{ + theme: theme.light_theme finder: MockFilesFinder{} } diff --git a/input_field.v b/input_field.v index 13da0ca9..24485076 100644 --- a/input_field.v +++ b/input_field.v @@ -3,7 +3,6 @@ module boba import math import time import tauraamui.bobatea as tea -import palette const frames_per_cycle = 50.0 @@ -14,23 +13,19 @@ mut: value string width int layout tea.Layout = no_bordered_layout - input_prefix string = ">" - prefix_padding int = 1 + input_prefix string = '>' + prefix_padding int = 1 cursor_pos int cursor_blink_frame int focused bool } -const subtle_bordered_layout = tea.new_layout() - .border(.normal) - .border_color(palette.subtle_border_fg_color) - const no_bordered_layout = tea.new_layout() .border(.none) -pub fn BorderedInputField.new() InputField { +pub fn BorderedInputField.new(border_color tea.Color) InputField { return InputField{ - layout: subtle_bordered_layout + layout: tea.new_layout().border(.normal).border_color(border_color) } } @@ -39,7 +34,10 @@ pub fn InputField.new() InputField { } pub fn InputField.new_with_prefix(input_prefix string, prefix_padding int) InputField { - return InputField{ input_prefix: input_prefix, prefix_padding: prefix_padding } + return InputField{ + input_prefix: input_prefix + prefix_padding: prefix_padding + } } pub fn (mut i InputField) init() ?tea.Cmd { @@ -141,7 +139,9 @@ pub fn (m InputField) view(mut r_ctx tea.Context) { // let's also make sure we notice if width is ever negative // because that's a bug signifier assert !(width < 0) - if width <= 0 { return } + if width <= 0 { + return + } cursor_pos := m.cursor_pos cursor_color := calculate_cursor_color(m.cursor_blink_frame) @@ -150,15 +150,17 @@ pub fn (m InputField) view(mut r_ctx tea.Context) { prefix_padding := m.prefix_padding height := if m.layout.border == .none { 1 } else { 3 } - m.layout.size(width, height).render(mut r_ctx, fn [ - cursor_pos, cursor_color, width, value_runes, input_prefix, prefix_padding - ] (mut l_ctx tea.Context) { + m.layout.size(width, height).render(mut r_ctx, fn [cursor_pos, cursor_color, width, value_runes, input_prefix, prefix_padding] (mut l_ctx tea.Context) { l_ctx.set_clip_area(tea.ClipArea{0, 0, width - 3, 1}) defer { l_ctx.clear_clip_area() } - l_ctx.draw_rect(0, 0, width - 2, 1) + + left_right_border_cells_to_deduct_from_rects_full_width := 2 + l_ctx.draw_rect(0, 0, width - left_right_border_cells_to_deduct_from_rects_full_width, 1) l_ctx.draw_text(0, 0, input_prefix) - input_text_offset := l_ctx.push_offset(tea.Offset{ x: prefix_padding + tea.visible_len(input_prefix) }) + input_text_offset := l_ctx.push_offset(tea.Offset{ + x: prefix_padding + tea.visible_len(input_prefix) + }) cursor_within_content := cursor_pos < value_runes.len for i, r in value_runes { r_str := r.str() @@ -200,7 +202,7 @@ pub fn (m &InputField) rune_len() int { } pub fn (mut m InputField) reset() { - m.value = '' + m.value = '' m.cursor_pos = 0 } @@ -221,4 +223,3 @@ fn (m InputField) clone() InputField { ...m } } - diff --git a/main.v b/main.v index 314eee5f..07f494ed 100644 --- a/main.v +++ b/main.v @@ -1,11 +1,12 @@ module main +import os import tauraamui.bobatea as tea import cfg fn main() { - // config := cfg.Config.new(load_from_path: none).set_theme(cfg.light_theme_name) - config := cfg.Config.new(load_from_path: none) + theme_name := os.getenv("PETAL_THEME") + config := cfg.Config.new(load_from_path: none).set_theme(theme_name) mut petal_model := PetalModel.new(config) mut app := tea.new_program(mut petal_model) diff --git a/make.vsh b/make.vsh index 12ff13b1..ff279aa2 100755 --- a/make.vsh +++ b/make.vsh @@ -17,6 +17,8 @@ context.task( run: |self| system('v . -o ${app_name}') ) context.task(name: 'run', depends: ['_generate-git-hash'], run: |self| system('v -g run .')) +context.task(name: 'run-d', depends: ['_generate-git-hash'], run: |self| system('export PETAL_THEME=dark && v -g run .')) +context.task(name: 'run-l', depends: ['_generate-git-hash'], run: |self| system('export PETAL_THEME=light && v -g run .')) context.task(name: 'compile-make', run: |self| system('v -prod -skip-running make.vsh')) // TEST TASKS @@ -61,6 +63,13 @@ context.task( } ) +context.task( + name: 'ansi-color-codes', + run: fn [mut context] (self build.Task) ! { + context.exec('ansi-colour-codes') + } +) + context.task( name: 'ansi-to-rgb' help: 'prompts for single ansi colour code and outputs the RGB components' diff --git a/mode.v b/mode.v index 280c7f9a..59ff401f 100644 --- a/mode.v +++ b/mode.v @@ -2,6 +2,7 @@ module main import tauraamui.bobatea as tea import palette +import theme enum Mode as u8 { normal @@ -13,27 +14,26 @@ enum Mode as u8 { navigation } -fn (m Mode) color() tea.Color { +fn (m Mode) color(ttheme theme.Theme) tea.Color { return match m { - .normal { palette.status_green } - .leader { palette.status_purple } - .command { palette.status_cyan } - .insert { palette.status_orange } - .visual { palette.status_lilac } - .visual_line { palette.status_lilac } - .navigation { palette.status_cyan } + .normal { ttheme.status_green } + .leader { ttheme.status_purple } + .command { ttheme.status_cyan } + .insert { ttheme.status_orange } + .visual { ttheme.status_lilac } + .visual_line { ttheme.status_lilac } + .navigation { ttheme.status_cyan } } } fn (m Mode) str() string { return match m { - .normal { 'NORMAL' } - .leader { 'LEADER' } - .command { 'COMMAND' } - .insert { 'INSERT' } - .visual { 'VISUAL' } + .normal { 'NORMAL' } + .leader { 'LEADER' } + .command { 'COMMAND' } + .insert { 'INSERT' } + .visual { 'VISUAL' } .visual_line { 'VISUAL LINE' } - .navigation { 'NAVIGATION' } + .navigation { 'NAVIGATION' } } } - diff --git a/petal.v b/petal.v index a972e59d..3bf4c451 100644 --- a/petal.v +++ b/petal.v @@ -2,8 +2,8 @@ module main import os import tauraamui.bobatea as tea -import cfg import theme +import cfg import palette const dot = '•' @@ -23,10 +23,10 @@ mut: fn PetalModel.new(config cfg.Config) PetalModel { return PetalModel{ - config: config - theme: config.theme - first_frame: true - active_screen: SplashScreenModel.new() + config: config + theme: config.theme + first_frame: true + active_screen: SplashScreenModel.new(leader_key: config.leader_key, theme: config.theme) } } diff --git a/splash_screen.v b/splash_screen.v index f0e910a8..3c873b0b 100644 --- a/splash_screen.v +++ b/splash_screen.v @@ -4,6 +4,7 @@ import math import os import tauraamui.bobatea as tea import palette +import theme const gitcommit_hash = $embed_file('.githash').to_string() @@ -18,9 +19,16 @@ mut: width int } +@[params] +struct SplashScreenOptions { + leader_key string + theme theme.Theme +} + struct SplashScreenModel { leader_key string logo SplashLogo + theme theme.Theme mut: tmux_wrapped bool leader_mode bool @@ -28,9 +36,10 @@ mut: dialog_model ?DebuggableModel } -fn SplashScreenModel.new() SplashScreenModel { +fn SplashScreenModel.new(opts SplashScreenOptions) SplashScreenModel { return SplashScreenModel{ - leader_key: ';' + leader_key: opts.leader_key + theme: opts.theme logo: SplashLogo{ data: logo_contents.to_string().split_into_lines() } @@ -43,7 +52,7 @@ fn (mut m SplashScreenModel) init() ?tea.Cmd { fn (mut m SplashScreenModel) handle_escape() (tea.Model, ?tea.Cmd) { if !m.leader_mode { - return SplashScreenModel{}, tea.quit + return m.clone(), tea.quit } m.leader_mode = false m.leader_data = '' @@ -61,13 +70,11 @@ fn (mut m SplashScreenModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { } if mut open_model := m.dialog_model { - intercepted_msg := if msg is tea.ResizedMsg { tea.Msg( - // force forward a 80% of the actual window size down to moddal model - tea.ResizedMsg{ - window_width: int(f64(msg.window_width) * 0.8) + // force forward a 80% of the actual window size down to moddal model + intercepted_msg := if msg is tea.ResizedMsg { tea.Msg(tea.ResizedMsg{ + window_width: int(f64(msg.window_width) * 0.8) window_height: int(f64(msg.window_height) * 0.8) - } - ) } else { msg } + }) } else { msg } d, cmd := open_model.update(intercepted_msg) if d is DebuggableModel { @@ -78,7 +85,7 @@ fn (mut m SplashScreenModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { match msg { CheckIfTMUXWrappedMsg { - m.tmux_wrapped = os.getenv("TMUX").len > 0 + m.tmux_wrapped = os.getenv('TMUX').len > 0 } tea.KeyMsg { match msg.k_type { @@ -115,7 +122,7 @@ fn (mut m SplashScreenModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { else { match msg.string() { 'q' { - return SplashScreenModel{}, tea.quit + return m.clone(), tea.quit } m.leader_key { if !m.leader_mode { @@ -141,7 +148,7 @@ fn (mut m SplashScreenModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { cmds << open_editor_workspace(msg.file_path) } OpenEditorWorkspaceMsg { - mut workspace := EditorWorkspaceModel.new(msg.initial_file_path) + mut workspace := EditorWorkspaceModel.new(m.theme, msg.initial_file_path) cmd := workspace.init() if u_cmd := cmd { cmds << u_cmd @@ -154,7 +161,7 @@ fn (mut m SplashScreenModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { match m.leader_data { 'ff' { m.reset_leader_mode() - cmds << open_file_picker + cmds << open_file_picker(m.theme) } 'xx' { m.reset_leader_mode() @@ -172,49 +179,62 @@ fn (mut m SplashScreenModel) reset_leader_mode() { } fn (m SplashScreenModel) view(mut ctx tea.Context) { - render_version_label(mut ctx, '${version} - (#${build_id})') - render_logo_and_help_centered_and_stacked(mut ctx, m.logo, m.leader_mode, m.leader_data) - render_help_keybinds(mut ctx) + render_version_label(mut ctx, '${version} - (#${build_id})', m.theme.subtle_light_grey) + render_logo_and_help_centered_and_stacked(mut ctx, + logo: m.logo + in_leader_mode: m.leader_mode + leader_data: m.leader_data + petal_pink: m.theme.petal_pink + petal_green: m.theme.petal_green + closest_match_color: m.theme.petal_green + disabled_help_fg_color: m.theme.subtle_light_grey + ) + render_help_keybinds(mut ctx, m.theme.subtle_light_grey) offset_from_id := ctx.push_offset(tea.Offset{ y: ctx.window_height() - 1 }) defer { ctx.clear_offsets_from(offset_from_id) } if m.leader_mode { ctx.set_color(palette.subtle_text_fg_color) - leader_data := ";" + m.leader_data + leader_data := m.leader_key + m.leader_data ctx.draw_text(ctx.window_width() - tea.visible_len(leader_data) - 1, 0, leader_data) ctx.reset_color() } ctx.clear_all_offsets() if mut open_model := m.dialog_model { - id := ctx.push_offset( - tea.Offset{ - x: int(f64(ctx.window_width() / 2)) - int(f64(open_model.width() / 2)) - y: int (f64(ctx.window_height() / 2)) - int(f64(open_model.height() / 2)) - } - ) + id := ctx.push_offset(tea.Offset{ + x: int(f64(ctx.window_width() / 2)) - int(f64(open_model.width() / 2)) + y: int(f64(ctx.window_height() / 2)) - int(f64(open_model.height() / 2)) + }) defer { ctx.clear_offsets_from(id) } open_model.view(mut ctx) } } -fn render_version_label(mut ctx tea.Context, version_label string) { - ctx.set_color(palette.help_fg_color) +fn render_version_label(mut ctx tea.Context, version_label string, help_fg_color tea.Color) { + ctx.set_color(help_fg_color) ctx.draw_text(1, 0, version_label) ctx.reset_color() } -fn render_help_keybinds(mut ctx tea.Context) { +fn render_help_keybinds(mut ctx tea.Context, help_fg_color tea.Color) { offset_from_id := ctx.push_offset(tea.Offset{ x: 1, y: ctx.window_height() - 1 }) defer { ctx.clear_offsets_from(offset_from_id) } - ctx.set_color(palette.help_fg_color) + ctx.set_color(help_fg_color) ctx.draw_text(0, 0, 'q: quit ${dot} esc: exit') ctx.reset_color() } -fn render_logo_and_help_centered_and_stacked(mut ctx tea.Context, logo SplashLogo, in_leader_mode bool, leader_data string) { +@[params] +struct RenderLogoAndHelpParams { + RenderLogoParams + RenderKeybindsListParams +} + +fn render_logo_and_help_centered_and_stacked(mut ctx tea.Context, + opts RenderLogoAndHelpParams) { // NOTE(tauraamui) [25/10/2025]: all following contents to be padded from top of window base_offset_y := f64(ctx.window_height()) * 0.1 offset_from_id := ctx.push_offset(tea.Offset{ @@ -223,22 +243,22 @@ fn render_logo_and_help_centered_and_stacked(mut ctx tea.Context, logo SplashLog }) defer { ctx.clear_offsets_from(offset_from_id) } - ctx.push_offset(render_logo(mut ctx, logo)) + ctx.push_offset(render_logo(mut ctx, opts.RenderLogoParams)) ctx.push_offset(render_lilly_name(mut ctx)) - ctx.push_offset(render_keybinds_list(mut ctx, in_leader_mode, leader_data)) - render_copyright_footer(mut ctx) + ctx.push_offset(render_keybinds_list(mut ctx, opts.RenderKeybindsListParams)) + render_copyright_footer(mut ctx, opts.petal_pink) } const copyright_footer_label = 'the lilly editor authors © (made with ${[u8(0xf0), 0x9f, 0x92, 0x95].bytestr()})' -fn render_copyright_footer(mut ctx tea.Context) { +fn render_copyright_footer(mut ctx tea.Context, petal_pink tea.Color) { offset_from_id := ctx.push_offset(tea.Offset{ x: -(tea.visible_len(copyright_footer_label) / 2) y: 1 }) defer { ctx.clear_offsets_from(offset_from_id) } - ctx.set_color(palette.petal_pink_color) + ctx.set_color(petal_pink) ctx.draw_text(0, 0, copyright_footer_label) ctx.reset_color() } @@ -256,21 +276,33 @@ const disabled_command_help = [ ] const pending_match_color = tea.Color.ansi(244) -const closest_match_color = palette.petal_green_color -fn render_keybinds_list(mut ctx tea.Context, in_leader_mode bool, leader_data string) tea.Offset { +@[params] +struct RenderKeybindsListParams { + in_leader_mode bool + leader_data string + closest_match_color tea.Color + disabled_help_fg_color tea.Color +} + +fn render_keybinds_list(mut ctx tea.Context, + opts RenderKeybindsListParams) tea.Offset { offset_from_id := ctx.push_offset(tea.Offset{ y: 1 }) defer { ctx.clear_offsets_from(offset_from_id) } for l in basic_command_help { ctx.push_offset(tea.Offset{ y: 1 }) ctx.push_offset(tea.Offset{ x: -(tea.visible_len(l) / 2) }) - if in_leader_mode { - fg_color := if leader_data == 'f' { closest_match_color } else { pending_match_color } + if opts.in_leader_mode { + fg_color := if opts.leader_data == 'f' { + opts.closest_match_color + } else { + pending_match_color + } ctx.set_color(fg_color) } ctx.draw_text(0, 0, l) - if in_leader_mode { + if opts.in_leader_mode { ctx.reset_color() } ctx.pop_offset() @@ -281,7 +313,7 @@ fn render_keybinds_list(mut ctx tea.Context, in_leader_mode bool, leader_data st ctx.push_offset(tea.Offset{ y: 1 }) ctx.push_offset(tea.Offset{ x: -(tea.visible_len(l) / 2) }) ctx.set_style(.strikethrough) - ctx.set_color(palette.help_fg_color) + ctx.set_color(opts.disabled_help_fg_color) ctx.draw_text(0, 0, l) ctx.reset_color() ctx.clear_style() @@ -307,7 +339,13 @@ fn render_lilly_name(mut ctx tea.Context) tea.Offset { return ctx.compact_offsets_from(offset_from_id) } -fn render_logo(mut ctx tea.Context, logo SplashLogo) tea.Offset { +@[params] +struct RenderLogoParams { + RenderLogoLineParams + logo SplashLogo +} + +fn render_logo(mut ctx tea.Context, opts RenderLogoParams) tea.Offset { // NOTE(tauraamui) [25/10/25]: this can be reduced to a style container which basically // makes the y offset be down by 10% of the parent. in this // case the parent is just the window itself, but could be anything @@ -316,41 +354,51 @@ fn render_logo(mut ctx tea.Context, logo SplashLogo) tea.Offset { // line at once with the light pink color set, but some lines of the logo // contain both green and pink, so they need to be rendered per character // with the correct palette option/fg set - ctx.set_color(palette.petal_pink_color) + // ctx.set_color(palette.petal_pink_color) + ctx.set_color(opts.petal_pink) offset_from_id := ctx.push_offset(tea.Offset{}) defer { ctx.clear_offsets_from(offset_from_id) } - for _, l in logo.data { + for _, l in opts.logo.data { // NOTE(tauraamui) [26/10/25] by splitting these offset pushes into two separate calls // we're only continuously removing the offset for the X position // each loop iter, so by the end `compact_offsets` is a combination of // the full height of the logo once its been completely rendered ctx.push_offset(tea.Offset{ y: 1 }) ctx.push_offset(tea.Offset{ x: -(tea.visible_len(l) / 2) }) - render_logo_line(mut ctx, l) + render_logo_line(mut ctx, l, opts.RenderLogoLineParams) ctx.pop_offset() } ctx.reset_color() return ctx.compact_offsets_from(offset_from_id) } -fn render_logo_line(mut ctx tea.Context, line string) { +@[params] +struct RenderLogoLineParams { + petal_pink tea.Color + petal_green tea.Color +} + +fn render_logo_line(mut ctx tea.Context, line string, opts RenderLogoLineParams) { if has_colouring_directives(line) { - render_logo_line_char_by_char(mut ctx, line) + render_logo_line_char_by_char(mut ctx, line, opts.petal_pink, opts.petal_green) return } ctx.draw_text(0, 0, line) } -fn render_logo_line_char_by_char(mut ctx tea.Context, line string) { +fn render_logo_line_char_by_char(mut ctx tea.Context, + line string, + petal_pink tea.Color, + petal_green tea.Color) { for j, c in line.runes() { mut to_draw := '${c}' if to_draw == 'g' { to_draw = ' ' - ctx.set_color(palette.petal_green_color) + ctx.set_color(petal_green) } if to_draw == 'p' { to_draw = ' ' - ctx.set_color(palette.petal_pink_color) + ctx.set_color(petal_pink) } ctx.draw_text(j, 0, to_draw) } @@ -369,17 +417,21 @@ fn (m SplashScreenModel) debug_data() DebugData { return DebugData{ name: 'splash_screen data' data: { - 'leader key': m.leader_key + 'leader key': m.leader_key 'tmux wrapped': '${m.tmux_wrapped}' - '': if d := m.dialog_model { d.debug_data() } else { 'null' } - 'version': '${version} - (${build_id})' + '': if d := m.dialog_model { d.debug_data() } else { 'null' } + 'version': '${version} - (${build_id})' } } } -fn (m SplashScreenModel) width() int { return 0 } +fn (m SplashScreenModel) width() int { + return 0 +} -fn (m SplashScreenModel) height() int { return 0 } +fn (m SplashScreenModel) height() int { + return 0 +} fn (m SplashScreenModel) clone() tea.Model { return SplashScreenModel{ diff --git a/split_tree.v b/split_tree.v index 13fe7c08..540e9593 100644 --- a/split_tree.v +++ b/split_tree.v @@ -65,7 +65,7 @@ pub fn (t SplitTree) find_editor_by_id(node SplitNode, target_id int) ?EditorInf EditorLeaf { if node.editor_id == target_id { return EditorInfo{ - id: node.editor_id + id: node.editor_id file_path: node.file_path } } @@ -145,8 +145,8 @@ fn (t SplitTree) insert_split_at(node SplitNode, target_id int, new_id int, new_ // Found target - wrap both in container return SplitContainer{ direction: direction - children: [SplitNode(node), SplitNode(new_leaf)] - ratios: [0.5, 0.5] + children: [SplitNode(node), SplitNode(new_leaf)] + ratios: [0.5, 0.5] } } return node @@ -169,14 +169,14 @@ fn (t SplitTree) insert_split_at(node SplitNode, target_id int, new_id int, new_ return SplitContainer{ ...node children: new_children - ratios: new_ratios + ratios: new_ratios } } else { // Different direction - wrap target and new in container new_container := SplitContainer{ direction: direction - children: [child, new_leaf] - ratios: [0.5, 0.5] + children: [child, new_leaf] + ratios: [0.5, 0.5] } mut new_children := node.children.clone() new_children[i] = new_container @@ -192,7 +192,8 @@ fn (t SplitTree) insert_split_at(node SplitNode, target_id int, new_id int, new_ // Not a direct child - recurse mut new_children := []SplitNode{} for child in node.children { - new_children << t.insert_split_at(child, target_id, new_id, new_file_path, direction) + new_children << t.insert_split_at(child, target_id, new_id, new_file_path, + direction) } return SplitContainer{ ...node @@ -264,7 +265,11 @@ pub fn (mut t SplitTree) navigate_prev(do_not_wrap_around bool) bool { return false } - prev_idx := if do_not_wrap_around { current_idx - 1 } else { (current_idx - 1 + all_ids.len) % all_ids.len } + prev_idx := if do_not_wrap_around { + current_idx - 1 + } else { + (current_idx - 1 + all_ids.len) % all_ids.len + } t.active_editor_id = all_ids[prev_idx] return true } @@ -311,10 +316,10 @@ fn (t SplitTree) calculate_layout(node SplitNode, x int, y int, width int, heigh EditorLeaf { rects << LayoutRect{ editor_id: node.editor_id - x: x - y: y - width: width - height: height + x: x + y: y + width: width + height: height } } SplitContainer { @@ -330,7 +335,8 @@ fn (t SplitTree) calculate_layout(node SplitNode, x int, y int, width int, heigh int(f64(width) * node.ratios[i]) } - t.calculate_layout(child, current_x, y, child_width, height, mut rects) + t.calculate_layout(child, current_x, y, child_width, height, mut + rects) current_x += child_width remaining_width -= child_width } @@ -348,7 +354,8 @@ fn (t SplitTree) calculate_layout(node SplitNode, x int, y int, width int, heigh int(f64(height) * node.ratios[i]) } - t.calculate_layout(child, x, current_y, width, child_height, mut rects) + t.calculate_layout(child, x, current_y, width, child_height, mut + rects) current_y += child_height remaining_height -= child_height } @@ -415,9 +422,9 @@ fn (t SplitTree) remove_editor_from_node(node SplitNode, target_id int) ?SplitNo } if new_children.len == 0 { - return none // container is empty + return none // container is empty } else if new_children.len == 1 { - return new_children[0] // collapse container with single child + return new_children[0] // collapse container with single child } else { // recalculate ratios mut new_ratios := []f64{} @@ -428,7 +435,7 @@ fn (t SplitTree) remove_editor_from_node(node SplitNode, target_id int) ?SplitNo return SplitContainer{ ...node children: new_children - ratios: new_ratios + ratios: new_ratios } } } @@ -438,4 +445,3 @@ fn (t SplitTree) remove_editor_from_node(node SplitNode, target_id int) ?SplitNo pub fn (t SplitTree) count() int { return t.get_all_editor_ids().len } - diff --git a/theme.v b/theme.v index a6d2cd4c..f8334dee 100644 --- a/theme.v +++ b/theme.v @@ -1,31 +1,76 @@ module theme import tauraamui.bobatea as tea -import palette pub const dark_theme_name = "dark" pub const light_theme_name = "light" -pub struct Theme{ +pub struct Theme { pub: - name string @[required] - bg_color tea.Color @[required] - fg_color tea.Color - active_split_divider_color tea.Color @[required] - inactive_split_divider_color tea.Color @[required] + name string @[required] + bg_color tea.Color @[required] + fg_color tea.Color + highlight_bg_color tea.Color @[required] + petal_pink tea.Color @[required] + petal_green tea.Color @[required] + petal_red tea.Color @[required] + subtle_light_grey tea.Color @[required] + + status_file_name tea.Color @[required] + status_branch_name tea.Color @[required] + status_bar_spacer tea.Color @[required] + + status_green tea.Color @[required] + status_purple tea.Color @[required] + status_cyan tea.Color @[required] + status_orange tea.Color @[required] + status_lilac tea.Color @[required] } +const dark_petal_pink = tea.Color.ansi(219) +const dark_petal_red = tea.Color.ansi(196) + pub const dark_theme = Theme{ name: "dark" - bg_color: palette.matte_black_bg_color - active_split_divider_color: palette.petal_pink_color - inactive_split_divider_color: palette.status_bar_bg_color + bg_color: tea.Color.ansi(233) + highlight_bg_color: tea.Color.ansi(139) + petal_pink: dark_petal_pink + petal_green: tea.Color.ansi(84) + petal_red: dark_petal_red + subtle_light_grey: tea.Color.ansi(241) + + status_file_name: tea.Color.ansi(239) + status_bar_spacer: tea.Color.ansi(234) + status_branch_name: dark_petal_pink + + status_green: tea.Color.ansi(120) + status_purple: tea.Color.ansi(105) + status_cyan: tea.Color.ansi(117) + status_orange: tea.Color.ansi(222) + status_lilac: tea.Color.ansi(134) } +const light_petal_pink = tea.Color.ansi(200) +const light_petal_green = tea.Color.ansi(76) +const light_subtle_light_grey = tea.Color.ansi(248) + pub const light_theme = Theme{ name: "light" - bg_color: palette.matte_white_fg_color - active_split_divider_color: palette.petal_pink_color - inactive_split_divider_color: palette.status_bar_bg_color + bg_color: tea.Color.ansi(231) + highlight_bg_color: tea.Color.ansi(218) + petal_pink: light_petal_pink + petal_green: light_petal_green + petal_red: dark_theme.petal_red + subtle_light_grey: light_subtle_light_grey + + status_file_name: tea.Color.ansi(242) + status_bar_spacer: tea.Color.ansi(255) + status_branch_name: tea.Color.ansi(219) + + status_green: tea.Color.ansi(120) + status_purple: tea.Color.ansi(105) + status_cyan: tea.Color.ansi(117) + status_orange: tea.Color.ansi(222) + status_lilac: tea.Color.ansi(134) } diff --git a/version_dialog.v b/version_dialog.v index 0fd6676d..7ae77938 100644 --- a/version_dialog.v +++ b/version_dialog.v @@ -1,18 +1,23 @@ module main import tauraamui.bobatea as tea +import theme struct VersionModel { + theme theme.Theme width int height int version string } -fn open_version_dialog() tea.Msg { - return OpenDialogMsg{ - model: VersionModel{ - width: 52 - height: 5 +fn open_version_dialog(ttheme theme.Theme) tea.Cmd { + return fn [ttheme] () tea.Msg { + return OpenDialogMsg{ + model: VersionModel{ + theme: ttheme + width: 52 + height: 5 + } } } } @@ -51,19 +56,23 @@ fn (mut m VersionModel) update(msg tea.Msg) (tea.Model, ?tea.Cmd) { fn (m VersionModel) view(mut r_ctx tea.Context) { r_ctx.draw_rect(0, 0, m.width, m.height) - width := m.width - 2 + width := m.width - 2 height := m.height - 2 - subtle_bordered_layout.size(m.width, m.height).render(mut r_ctx, fn [width, height] (mut ctx tea.Context) { + tea.new_layout().border(.normal).border_color(m.theme.petal_pink).size(m.width, m.height).render(mut r_ctx, fn [width, height] (mut ctx tea.Context) { ctx.reset_color() - version_label := "project petal version (${version})" + version_label := 'project petal version (${version})' ctx.draw_text((width / 2) - tea.visible_len(version_label) / 2, height / 2, version_label) }) } -fn (m VersionModel) width() int { return m.width } +fn (m VersionModel) width() int { + return m.width +} -fn (m VersionModel) height() int { return m.height } +fn (m VersionModel) height() int { + return m.height +} fn (m VersionModel) debug_data() DebugData { return DebugData{} @@ -74,5 +83,3 @@ fn (m VersionModel) clone() tea.Model { ...m } } - -