Add ability to save and configuratino file

Add ability to save and the start of a config file to config colours,
the font and the font size
master
Ronald 7 months ago
parent ccacafce64
commit 870c2169f1

@ -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
}

@ -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)
}

@ -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

@ -1,13 +1,14 @@
package main package main
import "core:fmt" import "core:fmt"
import "core:log"
import "core:math"
import "core:mem"
import "core:os" import "core:os"
import "core:strings"
import "core:slice" import "core:slice"
import "core:unicode/utf8"
import "core:strconv" import "core:strconv"
import "core:math" import "core:strings"
import "core:mem" import "core:unicode/utf8"
import sdl "vendor:sdl2" import sdl "vendor:sdl2"
import ttf "vendor:sdl2/ttf" import ttf "vendor:sdl2/ttf"
@ -20,58 +21,28 @@ LINE_NUMBER_WIDTH :: 60
FONT_SIZE :: 14 FONT_SIZE :: 14
TEXT_PADDING :: 10 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 app: App
main :: proc() { main :: proc() {
context.logger = log.create_console_logger(.Debug)
defer log.destroy_console_logger(context.logger)
// Get command line arguments // Get command line arguments
args := os.args[1:] 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 { if init_sdl() != 0 {
fmt.println("Failed to initialize SDL") log.fatal("failed to initialize sdl")
return return
} }
defer cleanup_sdl() defer cleanup_sdl()
@ -127,7 +98,7 @@ init_sdl :: proc() -> int {
} }
app.window = sdl.CreateWindow( app.window = sdl.CreateWindow(
"Multi-Tab Text Editor", "notepad",
sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED,
WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT,
sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE
@ -144,18 +115,13 @@ init_sdl :: proc() -> int {
return -1 return -1
} }
// Load font - try system fonts first, fallback to a basic one font_cstring := strings.clone_to_cstring(app.config.font)
font_paths := []string{ defer delete(font_cstring)
"/usr/share/fonts/TTF/FiraCode-Regular.ttf" fmt.println(font_cstring)
} app.font = ttf.OpenFont(font_cstring, i32(app.config.font_size))
for path in font_paths {
app.font = ttf.OpenFont(strings.clone_to_cstring(path), FONT_SIZE)
if app.font != nil do break
}
if app.font == nil { if app.font == nil {
fmt.println("Could not load any font - text rendering will fail") log.fatal("Could not load font")
return -1 return -1
} }
@ -467,7 +433,7 @@ handle_scroll :: proc(y: i32) {
} }
render :: proc() { 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) sdl.RenderClear(app.renderer)
render_tabs() render_tabs()
@ -482,16 +448,16 @@ render :: proc() {
render_tabs :: proc() { render_tabs :: proc() {
for i in 0..<len(app.tabs) { for i in 0..<len(app.tabs) {
tab := &app.tabs[i] tab := &app.tabs[i]
color := COLOR_TAB_INACTIVE if i != app.active_tab else COLOR_TAB_ACTIVE color := app.config.colours.inactive_tab if i != app.active_tab else app.config.colours.active_tab
sdl.SetRenderDrawColor(app.renderer, color.r, color.g, color.b, color.a) sdl.SetRenderDrawColor(app.renderer, color.r, color.g, color.b, color.a)
sdl.RenderFillRect(app.renderer, &tab.rect) sdl.RenderFillRect(app.renderer, &tab.rect)
sdl.SetRenderDrawColor(app.renderer, COLOR_TAB_BORDER.r, COLOR_TAB_BORDER.g, COLOR_TAB_BORDER.b, COLOR_TAB_BORDER.a) sdl.SetRenderDrawColor(app.renderer, app.config.colours.tab_border.r, app.config.colours.tab_border.g, app.config.colours.tab_border.b, app.config.colours.tab_border.a)
sdl.RenderDrawRect(app.renderer, &tab.rect) sdl.RenderDrawRect(app.renderer, &tab.rect)
// Render tab text // Render tab text
render_text(tab.name, tab.rect.x + 10, tab.rect.y + 5, COLOR_TEXT) render_text(tab.name, tab.rect.x + 10, tab.rect.y + 5, app.config.colours.text)
} }
} }
@ -500,11 +466,11 @@ render_text_area :: proc() {
// Render line number background // Render line number background
line_bg_rect := sdl.Rect{0, TAB_HEIGHT, LINE_NUMBER_WIDTH, WINDOW_HEIGHT - TAB_HEIGHT} line_bg_rect := sdl.Rect{0, TAB_HEIGHT, LINE_NUMBER_WIDTH, WINDOW_HEIGHT - TAB_HEIGHT}
sdl.SetRenderDrawColor(app.renderer, COLOR_LINE_NUMBER_BG.r, COLOR_LINE_NUMBER_BG.g, COLOR_LINE_NUMBER_BG.b, COLOR_LINE_NUMBER_BG.a) sdl.SetRenderDrawColor(app.renderer, app.config.colours.line_numbers_background.r, app.config.colours.line_numbers_background.g, app.config.colours.line_numbers_background.b, app.config.colours.line_numbers_background.a)
sdl.RenderFillRect(app.renderer, &line_bg_rect) sdl.RenderFillRect(app.renderer, &line_bg_rect)
// Render separator line // Render separator line
sdl.SetRenderDrawColor(app.renderer, COLOR_TAB_BORDER.r, COLOR_TAB_BORDER.g, COLOR_TAB_BORDER.b, COLOR_TAB_BORDER.a) sdl.SetRenderDrawColor(app.renderer, app.config.colours.tab_border.r, app.config.colours.tab_border.g, app.config.colours.tab_border.b, app.config.colours.tab_border.a)
sdl.RenderDrawLine(app.renderer, LINE_NUMBER_WIDTH, TAB_HEIGHT, LINE_NUMBER_WIDTH, WINDOW_HEIGHT) sdl.RenderDrawLine(app.renderer, LINE_NUMBER_WIDTH, TAB_HEIGHT, LINE_NUMBER_WIDTH, WINDOW_HEIGHT)
// Adjust scroll if cursor is outside visible area // Adjust scroll if cursor is outside visible area
@ -523,7 +489,7 @@ render_text_area :: proc() {
// Render line number // Render line number
line_num_str := fmt.tprintf("%d", i + 1) line_num_str := fmt.tprintf("%d", i + 1)
render_text(line_num_str, 5, y, COLOR_LINE_NUMBERS) render_text(line_num_str, 5, y, app.config.colours.line_numbers)
// Render line text with wrapping // Render line text with wrapping
if i < len(buffer.lines) { if i < len(buffer.lines) {
@ -535,7 +501,7 @@ render_text_area :: proc() {
cursor_x := LINE_NUMBER_WIDTH + TEXT_PADDING + i32(buffer.cursor_col) * app.char_width cursor_x := LINE_NUMBER_WIDTH + TEXT_PADDING + i32(buffer.cursor_col) * app.char_width
cursor_y := y cursor_y := y
sdl.SetRenderDrawColor(app.renderer, COLOR_CURSOR.r, COLOR_CURSOR.g, COLOR_CURSOR.b, COLOR_CURSOR.a) sdl.SetRenderDrawColor(app.renderer, app.config.colours.cursor.r, app.config.colours.cursor.g, app.config.colours.cursor.b, app.config.colours.cursor.a)
cursor_rect := sdl.Rect{cursor_x, cursor_y, 2, app.char_height} cursor_rect := sdl.Rect{cursor_x, cursor_y, 2, app.char_height}
sdl.RenderFillRect(app.renderer, &cursor_rect) sdl.RenderFillRect(app.renderer, &cursor_rect)
} }
@ -586,7 +552,7 @@ render_wrapped_line :: proc(line: string, x, y: i32, max_width: i32) {
} else { } else {
// Render current line and start new one // Render current line and start new one
if len(current_line) > 0 { 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)
line_y += app.char_height line_y += app.char_height
} }
current_line = word current_line = word
@ -595,8 +561,7 @@ render_wrapped_line :: proc(line: string, x, y: i32, max_width: i32) {
// Render final line // Render final line
if len(current_line) > 0 { 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)
} }
} }

@ -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,
}