diff --git a/colours.odin b/colours.odin new file mode 100644 index 0000000..11f6b85 --- /dev/null +++ b/colours.odin @@ -0,0 +1,42 @@ +package main + +import "core:encoding/hex" +import "core:log" +import "core:strings" + +hex_to_rgb :: proc(hex_str: string) -> (red, blue, green: u8, ok: bool) { + builder, err := strings.builder_make() + if err != nil { + log.error("failed to initialize memory") + return 0, 0, 0, false + } + + rgb_values: [3]u8 + rgb_count := 0 + for char, char_idx in hex_str { + if char == '#' { + continue + } + if rgb_count > 2 { + log.error("Malformed hex colour") + return 0, 0, 0, false + } + strings.write_rune(&builder, char) + if strings.builder_len(builder) == 2 { + str := strings.to_string(builder) + rgb_value, ok := hex.decode_sequence(str) + if !ok { + log.error("failed to initialize memory") + return 0, 0, 0, false + } + strings.builder_reset(&builder) + + rgb_values[rgb_count] = rgb_value + + rgb_count += 1 + } + } + + return rgb_values[0], rgb_values[1], rgb_values[2], true +} + diff --git a/config.odin b/config.odin new file mode 100644 index 0000000..6adb546 --- /dev/null +++ b/config.odin @@ -0,0 +1,80 @@ +package main + +import "core:encoding/hex" +import "core:encoding/ini" +import "core:fmt" +import "core:log" +import "core:mem" +import "core:strconv" +import "core:strings" +import sdl "vendor:sdl2" + +parse_config :: proc(config_path: string, allocator := context.allocator) -> (Config, bool) { + ini_map, err, ok := ini.load_map_from_path(config_path, allocator=allocator) + if err != nil { + log.error("failed to parse config file") + return {}, false + } + defer ini.delete_map(ini_map) + + config: Config + + config.font_size = DEFAULT_FONT_SIZE + + config.colours.background = DEFAULT_BACKGROUND_COLOUR + config.colours.text = DEFAULT_TEXT_COLOUR + config.colours.line_numbers = DEFAULT_LINE_NUMBERS_COLOUR + config.colours.line_numbers_background = DEFAULT_LINE_NUMBER_BG_COLOUR + config.colours.active_tab = DEFAULT_ACTIVE_TAB_COLOUR + config.colours.inactive_tab = DEFAULT_INACTIVE_TAB_COLOUR + config.colours.cursor = DEFAULT_CURSOR_COLOUR + + // TODO: Tidy this up + for section in ini_map { + lower_case_section := strings.to_lower(section) + defer delete(lower_case_section) + + if lower_case_section == "general" { + for key, value in ini_map[section] { + lower_case_key := strings.to_lower(key) + defer delete(lower_case_key) + + if lower_case_key == "font" { + fmt.println("test", ini_map[section][key]) + config.font = strings.clone(ini_map[section][key]) + } else if lower_case_key == "font size" { + config.font_size = strconv.atoi(ini_map[section][key]) + } + } + } else if lower_case_section == "colours" { + for key, value in ini_map[section] { + lower_case_key := strings.to_lower(key) + defer delete(lower_case_key) + + red, green, blue, ok := hex_to_rgb(ini_map[section][key]) + if !ok { + log.error("failed to parse hex value for", lower_case_key) + } + + if lower_case_key == "background" do config.colours.background = sdl.Colour{red, green, blue, 255} + else if lower_case_key == "text" do config.colours.text = sdl.Colour{red, green, blue, 255} + else if lower_case_key == "line numbers" do config.colours.line_numbers = sdl.Colour{red, green, blue, 255} + else if lower_case_key == "line number background" do config.colours.line_numbers_background = sdl.Colour{ + red, green, blue, 255 + } + else if lower_case_key == "active tab" do config.colours.active_tab = sdl.Colour{red, green, blue, 255} + else if lower_case_key == "inactive tab" do config.colours.inactive_tab = sdl.Colour{red, green, blue, 255} + else if lower_case_key == "tab border" do config.colours.tab_border = sdl.Colour{red, green, blue, 255} + else if lower_case_key == "cursor" do config.colours.cursor = sdl.Colour{red, green, blue, 255} + } + } + } + + if len(config.font) == 0 do config.font = strings.clone(DEFAULT_FONT) + + return config, true +} + +config_destroy :: proc(config: Config, allocator := context.allocator) { + delete(config.font) +} diff --git a/constants.odin b/constants.odin new file mode 100644 index 0000000..a82e885 --- /dev/null +++ b/constants.odin @@ -0,0 +1,18 @@ +package main + +import sdl "vendor:sdl2" + +// Colours +DEFAULT_BACKGROUND_COLOUR :: sdl.Color{26, 26, 26, 255} +DEFAULT_TEXT_COLOUR :: sdl.Color{220, 220, 220, 255} +DEFAULT_LINE_NUMBERS_COLOUR :: sdl.Color{150, 150, 150, 255} +DEFAULT_LINE_NUMBER_BG_COLOUR :: sdl.Color{40, 40, 40, 255} +DEFAULT_ACTIVE_TAB_COLOUR :: sdl.Color{60, 60, 60, 255} +DEFAULT_INACTIVE_TAB_COLOUR :: sdl.Color{45, 45, 45, 255} +DEFAULT_TAB_BORDER_COLOUR :: sdl.Color{80, 80, 80, 255} +DEFAULT_CURSOR_COLOUR :: sdl.Color{255, 255, 255, 255} + +// FONT +DEFAULT_FONT :: "/usr/share/fonts/TTF/FiraCode-Regular.ttf" +DEFAULT_FONT_SIZE :: 11 + diff --git a/main.odin b/main.odin index 83c3543..d431feb 100644 --- a/main.odin +++ b/main.odin @@ -1,13 +1,14 @@ package main import "core:fmt" +import "core:log" +import "core:math" +import "core:mem" import "core:os" -import "core:strings" import "core:slice" -import "core:unicode/utf8" import "core:strconv" -import "core:math" -import "core:mem" +import "core:strings" +import "core:unicode/utf8" import sdl "vendor:sdl2" import ttf "vendor:sdl2/ttf" @@ -20,58 +21,28 @@ LINE_NUMBER_WIDTH :: 60 FONT_SIZE :: 14 TEXT_PADDING :: 10 -// Colors -COLOR_BACKGROUND :: sdl.Color{30, 30, 30, 255} -COLOR_TEXT :: sdl.Color{220, 220, 220, 255} -COLOR_LINE_NUMBERS :: sdl.Color{150, 150, 150, 255} -COLOR_TAB_ACTIVE :: sdl.Color{60, 60, 60, 255} -COLOR_TAB_INACTIVE :: sdl.Color{45, 45, 45, 255} -COLOR_TAB_BORDER :: sdl.Color{80, 80, 80, 255} -COLOR_CURSOR :: sdl.Color{255, 255, 255, 255} -COLOR_LINE_NUMBER_BG :: sdl.Color{40, 40, 40, 255} - -// Text buffer for a single file -Text_Buffer :: struct { - lines: [dynamic]string, - filename: string, - modified: bool, - cursor_line: int, - cursor_col: int, - scroll_y: int, - max_line_width: int, -} - -// Tab structure -Tab :: struct { - name: string, - buffer: Text_Buffer, - rect: sdl.Rect, -} - -// Application state -App :: struct { - window: ^sdl.Window, - renderer: ^sdl.Renderer, - font: ^ttf.Font, - tabs: [dynamic]Tab, - active_tab: int, - running: bool, - char_width: i32, - char_height: i32, - text_area_width: i32, - visible_lines: int, - cursor_blink_timer: u32, - show_cursor: bool, -} - app: App main :: proc() { + context.logger = log.create_console_logger(.Debug) + defer log.destroy_console_logger(context.logger) + // Get command line arguments args := os.args[1:] + ok := false + app.config, ok = parse_config("config.ini") + if !ok { + log.fatal("failed to parse config file") + return + } + defer config_destroy(app.config) + + log.debug("app config:") + log.debug(app.config) + if init_sdl() != 0 { - fmt.println("Failed to initialize SDL") + log.fatal("failed to initialize sdl") return } defer cleanup_sdl() @@ -127,7 +98,7 @@ init_sdl :: proc() -> int { } app.window = sdl.CreateWindow( - "Multi-Tab Text Editor", + "notepad", sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE @@ -144,18 +115,13 @@ init_sdl :: proc() -> int { return -1 } - // Load font - try system fonts first, fallback to a basic one - font_paths := []string{ - "/usr/share/fonts/TTF/FiraCode-Regular.ttf" - } - - for path in font_paths { - app.font = ttf.OpenFont(strings.clone_to_cstring(path), FONT_SIZE) - if app.font != nil do break - } + font_cstring := strings.clone_to_cstring(app.config.font) + defer delete(font_cstring) + fmt.println(font_cstring) + app.font = ttf.OpenFont(font_cstring, i32(app.config.font_size)) if app.font == nil { - fmt.println("Could not load any font - text rendering will fail") + log.fatal("Could not load font") return -1 } @@ -467,7 +433,7 @@ handle_scroll :: proc(y: i32) { } render :: proc() { - sdl.SetRenderDrawColor(app.renderer, COLOR_BACKGROUND.r, COLOR_BACKGROUND.g, COLOR_BACKGROUND.b, COLOR_BACKGROUND.a) + sdl.SetRenderDrawColor(app.renderer, app.config.colours.background.r, app.config.colours.background.g, app.config.colours.background.b, app.config.colours.background.a) sdl.RenderClear(app.renderer) render_tabs() @@ -482,16 +448,16 @@ render :: proc() { render_tabs :: proc() { for i in 0.. 0 { - render_text(current_line, x, line_y, COLOR_TEXT) + render_text(current_line, x, line_y, app.config.colours.text) line_y += app.char_height } current_line = word @@ -595,8 +561,7 @@ render_wrapped_line :: proc(line: string, x, y: i32, max_width: i32) { // Render final line if len(current_line) > 0 { - render_text(current_line, x, line_y, COLOR_TEXT) + render_text(current_line, x, line_y, app.config.colours.text) } } - diff --git a/types.odin b/types.odin new file mode 100644 index 0000000..16b36bb --- /dev/null +++ b/types.odin @@ -0,0 +1,57 @@ +package main + +import sdl "vendor:sdl2" +import ttf "vendor:sdl2/ttf" + +// Text buffer for a single file +Text_Buffer :: struct { + lines: [dynamic]string, + filename: string, + modified: bool, + cursor_line: int, + cursor_col: int, + scroll_y: int, + max_line_width: int, +} + +// Tab structure +Tab :: struct { + name: string, + buffer: Text_Buffer, + rect: sdl.Rect, +} + +Colours :: struct { + background: sdl.Colour, + text: sdl.Colour, + line_numbers_background: sdl.Colour, + line_numbers: sdl.Colour, + active_tab: sdl.Colour, + inactive_tab: sdl.Colour, + tab_border: sdl.Colour, + cursor: sdl.Colour +} + +Config :: struct { + font: string, + font_size: int, + colours: Colours, +} + +// Application state +App :: struct { + window: ^sdl.Window, + renderer: ^sdl.Renderer, + font: ^ttf.Font, + tabs: [dynamic]Tab, + active_tab: int, + running: bool, + char_width: i32, + char_height: i32, + text_area_width: i32, + visible_lines: int, + cursor_blink_timer: u32, + show_cursor: bool, + config: Config, +} +