Add the start of this to a repository

add-themes
Ronald 7 months ago
commit 057d793c4c

@ -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,81 @@
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"
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
config.colours.highlight = DEFAULT_HIGHLIGHT_COLOUR
config.colours.status_bar = DEFAULT_STATUS_BAR_COLOUR
config.colours.status_bar_text = DEFAULT_STATUS_BAR_TEXT_COLUR
// 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" {
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 = Colour{red, green, blue, 255}
else if lower_case_key == "text" do config.colours.text = Colour{red, green, blue, 255}
else if lower_case_key == "line numbers" do config.colours.line_numbers = Colour{red, green, blue, 255}
else if lower_case_key == "line number background" do config.colours.line_numbers_background = Colour{
red, green, blue, 255
}
else if lower_case_key == "active tab" do config.colours.active_tab = Colour{red, green, blue, 255}
else if lower_case_key == "inactive tab" do config.colours.inactive_tab = Colour{red, green, blue, 255}
else if lower_case_key == "tab border" do config.colours.tab_border = Colour{red, green, blue, 255}
else if lower_case_key == "cursor" do config.colours.cursor = 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,23 @@
package main
// WINDOW SIZES
DEFAULT_WINDOW_MINIMUM_WINDOW_WIDTH :: 300
DEFAULT_WINDOW_MINIMUM_WINDOW_HEIGHT :: 200
// Gruvbox Dark Hard Color Scheme Constants
DEFAULT_BACKGROUND_COLOUR :: Colour{29, 32, 33, 255}
DEFAULT_TEXT_COLOUR :: Colour{227, 218, 186, 255}
DEFAULT_LINE_NUMBERS_COLOUR :: Colour{146, 131, 116, 255}
DEFAULT_LINE_NUMBER_BG_COLOUR :: Colour{40, 40, 40, 255}
DEFAULT_ACTIVE_TAB_COLOUR :: Colour{60, 56, 54, 255}
DEFAULT_INACTIVE_TAB_COLOUR :: Colour{50, 48, 47, 255}
DEFAULT_TAB_BORDER_COLOUR :: Colour{102, 92, 84, 255}
DEFAULT_CURSOR_COLOUR :: Colour{251, 241, 199, 255}
DEFAULT_HIGHLIGHT_COLOUR :: Colour{60, 56, 54, 255}
DEFAULT_STATUS_BAR_COLOUR :: Colour{60, 56, 54, 255}
DEFAULT_STATUS_BAR_TEXT_COLUR :: Colour{197, 197, 55, 255}
// FONT
DEFAULT_FONT :: "/usr/share/fonts/TTF/FiraCode-Regular.ttf"
DEFAULT_FONT_SIZE :: 11

@ -0,0 +1,787 @@
package main
import "core:fmt"
import "core:log"
import "core:os"
import "core:strings"
import "core:slice"
import "core:math"
import rl "vendor:raylib"
main :: proc() {
context.logger = log.create_console_logger(.Debug)
defer log.destroy_console_logger(context.logger)
args := os.args[1:]
if len(args) == 0 {
fmt.println("Usage: gui_cat <file1> [file2] [file3] ...")
return
}
config, ok := parse_config("config.ini")
if !ok {
log.fatal("failed to parse config file")
return
}
// Initialize application
app := App{
files = make([dynamic]FileBuffer),
fonts = make([dynamic]FontConfig),
active_tab = 0,
current_font_index = 0,
line_height = 24,
tab_height = 40,
window_width = 1200,
window_height = 800,
line_number_width = 30,
mouse_drag_start = -1,
is_dragging = false,
copied_text = "",
config = config
}
app.text_area_y = app.tab_height + 10
// Initialize Raylib
rl.InitWindow(app.window_width, app.window_height, "notepad_squared")
rl.SetWindowState(rl.ConfigFlags{.WINDOW_RESIZABLE})
rl.SetWindowMinSize(DEFAULT_WINDOW_MINIMUM_WINDOW_WIDTH, DEFAULT_WINDOW_MINIMUM_WINDOW_HEIGHT)
rl.SetTargetFPS(60)
// Load fonts
load_fonts(&app)
// Load files from command line arguments
for filename in args {
load_file(&app, filename)
}
if len(app.files) == 0 {
fmt.println("No files could be loaded")
rl.CloseWindow()
return
}
defer {
rl.CloseWindow()
delete(app.files)
delete(app.fonts)
}
// Main loop
for !rl.WindowShouldClose() {
update(&app)
draw(&app)
}
}
load_fonts :: proc(app: ^App) {
// TODO: Support loading multiple fonts
// TODO: Allow searching for a font based on it's name and search fc-list database
font := FontConfig{
font = rl.LoadFont(strings.clone_to_cstring(app.config.font)),
size = f32(app.config.font_size),
name = "Default",
}
append(&app.fonts, font)
app.line_height = app.fonts[app.current_font_index].size + 4
}
load_file :: proc(app: ^App, filename: string) {
data, ok := os.read_entire_file(filename)
if !ok {
fmt.printf("Failed to read file: %s\n", filename)
return
}
buffer := FileBuffer{
filename = strings.clone(filename),
content = string(data),
modified = false,
cursor_pos = 0,
scroll_y = 0,
selection = Selection{start = 0, end = 0, active = false},
}
append(&app.files, buffer)
}
get_wrapped_lines :: proc(content: string, font: ^FontConfig, max_width: f32) -> []WrappedLine {
lines := strings.split(content, "\n")
defer delete(lines)
wrapped_lines := make([dynamic]WrappedLine)
character_offset := 0
for line, line_index in lines {
if len(line) == 0 {
// Empty line
append(&wrapped_lines, WrappedLine{
text = "",
original_line = line_index,
character_offset = character_offset,
})
} else {
// Wrap long lines
current_pos := 0
for current_pos < len(line) {
// Find how many characters fit in the width
fit_characters := 0
current_width: f32 = 0
for i in current_pos..<len(line) {
character_width := rl.MeasureTextEx(font.font, strings.clone_to_cstring(fmt.tprint(rune(line[i]))), font.size, 1).x
if current_width + character_width > max_width && fit_characters > 0 {
break
}
current_width += character_width
fit_characters += 1
}
// If we couldn't fit even one character, force at least one
if fit_characters == 0 {
fit_characters = 1
}
// Extract the text that fits
end_pos := min(current_pos + fit_characters, len(line))
wrapped_text := line[current_pos:end_pos]
append(&wrapped_lines, WrappedLine{
text = wrapped_text,
original_line = line_index,
character_offset = character_offset + current_pos,
})
current_pos = end_pos
}
}
character_offset += len(line) + 1 // +1 for newline
}
return wrapped_lines[:]
}
get_character_at_position :: proc(app: ^App, mouse_pos: rl.Vector2) -> int {
if len(app.files) == 0 || app.active_tab >= len(app.files) do return 0
file := &app.files[app.active_tab]
font := &app.fonts[app.current_font_index]
content_area_x := 10 + app.line_number_width
content_area_y := app.text_area_y
content_width := f32(app.window_width - 20) - app.line_number_width - 10
if mouse_pos.x < content_area_x || mouse_pos.y < content_area_y do return 0
wrapped_lines := get_wrapped_lines(file.content, font, content_width)
defer delete(wrapped_lines)
y_pos := content_area_y - file.scroll_y + 5
for wrapped_line, i in wrapped_lines {
if mouse_pos.y >= y_pos && mouse_pos.y < y_pos + app.line_height {
// Found the line, now find the character
x_offset := mouse_pos.x - content_area_x - 5
if x_offset <= 0 {
return wrapped_line.character_offset
}
for j in 0..=len(wrapped_line.text) {
text_slice := wrapped_line.text[:j]
text_width := rl.MeasureTextEx(font.font, strings.clone_to_cstring(text_slice), font.size, 1).x
if x_offset <= text_width {
return wrapped_line.character_offset + j
}
}
return wrapped_line.character_offset + len(wrapped_line.text)
}
y_pos += app.line_height
}
return len(file.content)
}
update :: proc(app: ^App) {
// Handle window resize
if rl.IsWindowResized() {
app.window_width = rl.GetScreenWidth()
app.window_height = rl.GetScreenHeight()
}
// Handle tab switching with mouse
if rl.IsMouseButtonPressed(.LEFT) {
mouse_pos := rl.GetMousePosition()
if mouse_pos.y <= app.tab_height {
tab_width := f32(app.window_width) / f32(len(app.files))
clicked_tab := int(mouse_pos.x / tab_width)
if clicked_tab >= 0 && clicked_tab < len(app.files) {
app.active_tab = clicked_tab
}
}
}
// Handle text selection with mouse
if len(app.files) > 0 && app.active_tab < len(app.files) {
current_file := &app.files[app.active_tab]
mouse_pos := rl.GetMousePosition()
if rl.IsMouseButtonPressed(.LEFT) {
if mouse_pos.y > app.text_area_y {
character_pos := get_character_at_position(app, mouse_pos)
current_file.cursor_pos = character_pos
current_file.selection.start = character_pos
current_file.selection.end = character_pos
current_file.selection.active = false
app.mouse_drag_start = character_pos
app.is_dragging = true
}
}
if app.is_dragging && rl.IsMouseButtonDown(.LEFT) {
if mouse_pos.y > app.text_area_y {
character_pos := get_character_at_position(app, mouse_pos)
current_file.cursor_pos = character_pos
if character_pos != app.mouse_drag_start {
current_file.selection.start = min(app.mouse_drag_start, character_pos)
current_file.selection.end = max(app.mouse_drag_start, character_pos)
current_file.selection.active = true
}
}
}
if rl.IsMouseButtonReleased(.LEFT) {
app.is_dragging = false
}
}
// Handle keyboard input for active file
if len(app.files) > 0 && app.active_tab < len(app.files) {
current_file := &app.files[app.active_tab]
// Handle text input
key := rl.GetCharPressed()
for key != 0 {
if key >= 32 && key <= 126 { // Printable ASCII
if current_file.selection.active {
delete_selection(current_file)
}
insert_character(current_file, rune(key))
}
key = rl.GetCharPressed()
}
// Handle special keys
if rl.IsKeyPressed(.ENTER) {
if current_file.selection.active {
delete_selection(current_file)
}
insert_character(current_file, '\n')
}
if rl.IsKeyPressed(.BACKSPACE) {
if current_file.selection.active {
delete_selection(current_file)
} else {
delete_character(current_file)
}
}
if rl.IsKeyPressed(.TAB) {
if current_file.selection.active {
delete_selection(current_file)
}
insert_character(current_file, '\t')
}
// Handle cursor movement
shift_held := rl.IsKeyDown(.LEFT_SHIFT) || rl.IsKeyDown(.RIGHT_SHIFT)
if rl.IsKeyPressed(.LEFT) {
if !shift_held && current_file.selection.active {
current_file.cursor_pos = current_file.selection.start
current_file.selection.active = false
} else {
if !current_file.selection.active && shift_held {
current_file.selection.start = current_file.cursor_pos
current_file.selection.active = true
}
if current_file.cursor_pos > 0 {
current_file.cursor_pos -= 1
}
if shift_held {
current_file.selection.end = current_file.cursor_pos
}
}
}
if rl.IsKeyPressed(.RIGHT) {
if !shift_held && current_file.selection.active {
current_file.cursor_pos = current_file.selection.end
current_file.selection.active = false
} else {
if !current_file.selection.active && shift_held {
current_file.selection.start = current_file.cursor_pos
current_file.selection.active = true
}
if current_file.cursor_pos < len(current_file.content) {
current_file.cursor_pos += 1
}
if shift_held {
current_file.selection.end = current_file.cursor_pos
}
}
}
if rl.IsKeyPressed(.UP) {
if !shift_held && current_file.selection.active {
current_file.selection.active = false
} else if !current_file.selection.active && shift_held {
current_file.selection.start = current_file.cursor_pos
current_file.selection.active = true
}
move_cursor_up(current_file)
if shift_held {
current_file.selection.end = current_file.cursor_pos
}
}
if rl.IsKeyPressed(.DOWN) {
if !shift_held && current_file.selection.active {
current_file.selection.active = false
} else if !current_file.selection.active && shift_held {
current_file.selection.start = current_file.cursor_pos
current_file.selection.active = true
}
move_cursor_down(current_file)
if shift_held {
current_file.selection.end = current_file.cursor_pos
}
}
// Handle scrolling
wheel := rl.GetMouseWheelMove()
current_file.scroll_y -= wheel * app.line_height * 3
current_file.scroll_y = max(0, current_file.scroll_y)
// Handle keyboard shortcuts
if rl.IsKeyDown(.LEFT_CONTROL) {
if rl.IsKeyPressed(.S) {
save_file(current_file)
}
if rl.IsKeyPressed(.A) {
select_all(current_file)
}
if rl.IsKeyPressed(.C) {
copy_selection(app, current_file)
}
if rl.IsKeyPressed(.V) {
paste_text(current_file, app.copied_text)
}
if rl.IsKeyPressed(.TAB) {
app.active_tab = (app.active_tab + 1) % len(app.files)
}
}
// Handle font switching (F1-F9)
for i in 0..<min(9, len(app.fonts)) {
if rl.IsKeyPressed(rl.KeyboardKey(int(rl.KeyboardKey.F1) + i)) {
app.current_font_index = i
app.line_height = app.fonts[app.current_font_index].size + 4
}
}
}
}
draw :: proc(app: ^App) {
rl.BeginDrawing()
rl.ClearBackground(app.config.colours.background)
// Draw tabs
draw_tabs(app)
// Draw active file content
if len(app.files) > 0 && app.active_tab < len(app.files) {
draw_file_content(app, &app.files[app.active_tab])
}
// Draw status bar
draw_status_bar(app)
rl.EndDrawing()
}
draw_tabs :: proc(app: ^App) {
if len(app.files) == 0 do return
tab_width := f32(app.window_width) / f32(len(app.files))
for i in 0..<len(app.files) {
x := f32(i) * tab_width
colours := app.config.colours
// Tab background
colour := colours.active_tab if i == app.active_tab else colours.inactive_tab
rl.DrawRectangle(i32(x), 0, i32(tab_width), i32(app.tab_height), colour)
// Tab border
rl.DrawRectangleLines(i32(x), 0, i32(tab_width), i32(app.tab_height), colour)
// Tab text
filename := app.files[i].filename
if len(filename) > 20 {
filename = fmt.tprintf("...%s", filename[len(filename)-17:])
}
// Add asterisk if modified
display_name := filename
if app.files[i].modified {
display_name = fmt.tprintf("%s*", filename)
}
font := &app.fonts[app.current_font_index]
text_size := rl.MeasureTextEx(font.font, strings.clone_to_cstring(display_name), font.size, 1)
text_x := x + tab_width/2 - text_size.x/2
text_y := app.tab_height/2 - text_size.y/2
rl.DrawTextEx(
font.font,
strings.clone_to_cstring(display_name),
{text_x, text_y},
font.size,
1,
colours.text
)
}
}
draw_file_content :: proc(app: ^App, file: ^FileBuffer) {
font := &app.fonts[app.current_font_index]
// Content area (excluding line numbers)
content_area := rl.Rectangle{
x = 10 + app.line_number_width,
y = app.text_area_y,
width = f32(app.window_width - 20) - app.line_number_width,
height = f32(app.window_height) - app.text_area_y - 30,
}
// Line number area
line_num_area := rl.Rectangle{
x = 10,
y = app.text_area_y,
width = app.line_number_width,
height = f32(app.window_height) - app.text_area_y - 30,
}
// Background
colours := app.config.colours
rl.DrawRectangleRec(line_num_area, colours.line_numbers_background)
rl.DrawRectangleLinesEx(line_num_area, 2, colours.line_numbers_background)
rl.DrawRectangleRec(content_area, colours.background)
rl.DrawRectangleLinesEx(content_area, 2, colours.background)
// Get wrapped lines
content_width := content_area.width
wrapped_lines := get_wrapped_lines(file.content, font, content_width)
defer delete(wrapped_lines)
// Enable scissor test for scrolling
rl.BeginScissorMode(
i32(content_area.x),
i32(content_area.y),
i32(content_area.width),
i32(content_area.height)
)
// Draw text content and selection
y_pos := content_area.y - file.scroll_y + 5
cursor_y := y_pos
cursor_x := content_area.x + 5
for wrapped_line, i in wrapped_lines {
if y_pos > content_area.y + content_area.height {
break
}
if y_pos + app.line_height > content_area.y {
// Draw selection background for this line
if file.selection.active {
line_start := wrapped_line.character_offset
line_end := wrapped_line.character_offset + len(wrapped_line.text)
if file.selection.start <= line_end && file.selection.end >= line_start {
sel_start_in_line := max(0, file.selection.start - line_start)
sel_end_in_line := min(len(wrapped_line.text), file.selection.end - line_start)
if sel_start_in_line < sel_end_in_line {
before_sel := wrapped_line.text[:sel_start_in_line]
sel_x := content_area.x + 5 +
rl.MeasureTextEx(font.font, strings.clone_to_cstring(before_sel), font.size, 1).x
selected_text := wrapped_line.text[sel_start_in_line:sel_end_in_line]
sel_width := rl.MeasureTextEx(font.font, strings.clone_to_cstring(selected_text), font.size, 1).x
rl.DrawRectangle(
i32(sel_x),
i32(y_pos),
i32(sel_width),
i32(app.line_height),
app.config.colours.highlight
)
}
}
}
// Draw text
rl.DrawTextEx(
font.font,
strings.clone_to_cstring(wrapped_line.text),
{content_area.x + 5, y_pos},
font.size,
1,
app.config.colours.text
)
}
// Calculate cursor position
if wrapped_line.character_offset <= file.cursor_pos &&
file.cursor_pos <= wrapped_line.character_offset + len(wrapped_line.text) {
cursor_y = y_pos
cursor_character_pos := file.cursor_pos - wrapped_line.character_offset
cursor_text := wrapped_line.text[:cursor_character_pos] if cursor_character_pos <= len(wrapped_line.text) else wrapped_line.text
cursor_x = content_area.x + 5 + rl.MeasureTextEx(font.font, strings.clone_to_cstring(cursor_text), font.size, 1).x
}
y_pos += app.line_height
}
// Draw cursor
if !file.selection.active && int(rl.GetTime() * 2) % 2 == 0 { // Blinking cursor
rl.DrawLine(i32(cursor_x), i32(cursor_y), i32(cursor_x), i32(cursor_y + app.line_height), rl.RED)
}
rl.EndScissorMode()
// Draw line numbers (show original line numbers)
rl.BeginScissorMode(i32(line_num_area.x), i32(line_num_area.y),
i32(line_num_area.width), i32(line_num_area.height))
y_pos = line_num_area.y - file.scroll_y + 5
current_original_line := -1
for wrapped_line, i in wrapped_lines {
if y_pos > line_num_area.y + line_num_area.height {
break
}
if y_pos + app.line_height > line_num_area.y {
// Only show line number for the first wrapped line of each original line
if wrapped_line.original_line != current_original_line {
current_original_line = wrapped_line.original_line
line_num_text := fmt.tprintf("%d", wrapped_line.original_line + 1)
text_size := rl.MeasureTextEx(font.font, strings.clone_to_cstring(line_num_text), font.size, 1)
text_x := line_num_area.x + line_num_area.width - text_size.x - 5
rl.DrawTextEx(
font.font,
strings.clone_to_cstring(line_num_text),
{text_x, y_pos},
font.size,
1,
app.config.colours.line_numbers
)
}
}
y_pos += app.line_height
}
rl.EndScissorMode()
}
draw_status_bar :: proc(app: ^App) {
status_y := app.window_height - 25
rl.DrawRectangle(0, status_y, app.window_width, 25, app.config.colours.status_bar)
if len(app.files) > 0 && app.active_tab < len(app.files) {
file := &app.files[app.active_tab]
font_name := app.fonts[app.current_font_index].name
selection_info := ""
if file.selection.active {
selection_info = fmt.tprintf(" | Selected: %d characters", file.selection.end - file.selection.start)
}
status_text := fmt.tprintf(
"File: %s | Font: %s (F1-F%d) | Cursor: %d%s | Ctrl+S: Save | Ctrl+A: Select All | Ctrl+C/V: Copy/Paste",
file.filename,
font_name,
len(app.fonts),
file.cursor_pos,
selection_info
)
font := &app.fonts[app.current_font_index]
rl.DrawTextEx(
font.font,
strings.clone_to_cstring(status_text),
{f32(5), f32(status_y + 5)},
font.size,
1,
app.config.colours.status_bar_text
)
}
}
insert_character :: proc(file: ^FileBuffer, character: rune) {
if file.cursor_pos <= len(file.content) {
before := file.content[:file.cursor_pos]
after := file.content[file.cursor_pos:]
file.content = fmt.aprintf("%s%c%s", before, character, after)
file.cursor_pos += 1
file.modified = true
clear_selection(file)
}
}
delete_character :: proc(file: ^FileBuffer) {
if file.cursor_pos > 0 {
before := file.content[:file.cursor_pos-1]
after := file.content[file.cursor_pos:]
file.content = fmt.aprintf("%s%s", before, after)
file.cursor_pos -= 1
file.modified = true
clear_selection(file)
}
}
delete_selection :: proc(file: ^FileBuffer) {
if !file.selection.active do return
start := min(file.selection.start, file.selection.end)
end := max(file.selection.start, file.selection.end)
before := file.content[:start]
after := file.content[end:]
file.content = fmt.aprintf("%s%s", before, after)
file.cursor_pos = start
file.modified = true
clear_selection(file)
}
clear_selection :: proc(file: ^FileBuffer) {
file.selection.active = false
file.selection.start = 0
file.selection.end = 0
}
select_all :: proc(file: ^FileBuffer) {
file.selection.start = 0
file.selection.end = len(file.content)
file.selection.active = true
file.cursor_pos = len(file.content)
}
copy_selection :: proc(app: ^App, file: ^FileBuffer) {
if !file.selection.active do return
start := min(file.selection.start, file.selection.end)
end := max(file.selection.start, file.selection.end)
app.copied_text = file.content[start:end]
// Set clipboard (Raylib function)
rl.SetClipboardText(strings.clone_to_cstring(app.copied_text))
}
paste_text :: proc(file: ^FileBuffer, text: string) {
if file.selection.active {
delete_selection(file)
}
// Get clipboard text
clipboard := rl.GetClipboardText()
if clipboard != nil {
text_to_paste := string(clipboard)
before := file.content[:file.cursor_pos]
after := file.content[file.cursor_pos:]
file.content = fmt.aprintf("%s%s%s", before, text_to_paste, after)
file.cursor_pos += len(text_to_paste)
file.modified = true
}
}
move_cursor_up :: proc(file: ^FileBuffer) {
// For wrapped text, we need to handle cursor movement differently
// This is a simplified version - you may want to enhance it further
if file.cursor_pos > 0 {
// Find the previous newline
for i := file.cursor_pos - 1; i >= 0; i -= 1 {
if file.content[i] == '\n' {
// Move to the previous line
prev_newline := -1
for j := i - 1; j >= 0; j -= 1 {
if file.content[j] == '\n' {
prev_newline = j
break
}
}
current_pos_in_line := file.cursor_pos - i - 1
prev_line_length := i - prev_newline - 1
new_pos_in_line := min(current_pos_in_line, prev_line_length)
file.cursor_pos = prev_newline + 1 + new_pos_in_line
return
}
}
// If no newline found, go to beginning
file.cursor_pos = 0
}
}
move_cursor_down :: proc(file: ^FileBuffer) {
// For wrapped text, we need to handle cursor movement differently
// This is a simplified version - you may want to enhance it further
if file.cursor_pos < len(file.content) {
// Find the current line start
current_line_start := 0
for i := file.cursor_pos - 1; i >= 0; i -= 1 {
if file.content[i] == '\n' {
current_line_start = i + 1
break
}
}
// Find the next newline
for i := file.cursor_pos; i < len(file.content); i += 1 {
if file.content[i] == '\n' {
// Move to the next line
next_newline := len(file.content)
for j := i + 1; j < len(file.content); j += 1 {
if file.content[j] == '\n' {
next_newline = j
break
}
}
current_pos_in_line := file.cursor_pos - current_line_start
next_line_length := next_newline - i - 1
new_pos_in_line := min(current_pos_in_line, next_line_length)
file.cursor_pos = i + 1 + new_pos_in_line
return
}
}
// If no newline found, go to end
file.cursor_pos = len(file.content)
}
}
save_file :: proc(file: ^FileBuffer) {
success := os.write_entire_file(file.filename, transmute([]u8)file.content)
if success {
file.modified = false
fmt.printf("Saved: %s\n", file.filename)
} else {
fmt.printf("Failed to save: %s\n", file.filename)
}
}

@ -0,0 +1,74 @@
package main
import rl "vendor:raylib"
Colour :: rl.Color
// Font configuration
FontConfig :: struct {
font: rl.Font,
size: f32,
name: string,
}
// Text selection
Selection :: struct {
start: int,
end: int,
active: bool,
}
// Wrapped line information
WrappedLine :: struct {
text: string,
original_line: int,
character_offset: int,
}
// File buffer structure
FileBuffer :: struct {
filename: string,
content: string,
modified: bool,
cursor_pos: int,
scroll_y: f32,
selection: Selection,
}
Colours :: struct {
background: Colour,
text: Colour,
line_numbers_background: Colour,
line_numbers: Colour,
highlight: Colour,
status_bar: Colour,
status_bar_text: Colour,
active_tab: Colour,
inactive_tab: Colour,
tab_border: Colour,
cursor: Colour
}
Config :: struct {
font: string,
font_size: int,
colours: Colours,
}
// Application state
App :: struct {
files: [dynamic]FileBuffer,
active_tab: int,
fonts: [dynamic]FontConfig,
current_font_index: int,
line_height: f32,
tab_height: f32,
window_width: i32,
window_height: i32,
text_area_y: f32,
line_number_width: f32,
mouse_drag_start: int,
is_dragging: bool,
copied_text: string,
config: Config,
}
Loading…
Cancel
Save