use_enum_names = true,
}
-Nav_Action :: enum {
- up,
- down,
- select,
- escape,
-}
-
App :: struct {
mutex: sync.Mutex,
running: bool,
seed_phrase_entropy: u128,
seed_phrase_checksum: u8,
- connecting: bool,
host: Maybe(net.TCP_Socket),
- connection_thread: Maybe(^thread.Thread),
- nav_action: Nav_Action,
- selected_room: u32,
+ selected_room: int,
}
app_init :: proc(storage_path: string) -> App {
sync.mutex_lock(&app.mutex)
defer sync.mutex_unlock(&app.mutex)
- if connection_thread, ok := app.connection_thread.?; ok {
- thread.destroy(connection_thread)
- }
-
if host, ok := app.host.?; ok {
net.close(host)
}
return
}
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
+
nc.wclear(app.room_list_window)
c_room_name_buffer: [ROOM_NAME_MAX_SIZE + 1]u8
for &room, i in app.profile.rooms {
room_color := nc.COLOR_PAIR(3)
- if u32(i) == app.selected_room {
+ if i == app.selected_room {
room_color = nc.COLOR_PAIR(1)
}
mem.set(raw_data(c_room_name_buffer[:]), ' ', len(c_room_name_buffer))
mem.copy_non_overlapping(raw_data(c_room_name_buffer[1:]), raw_data(room_name_bytes), len(room_name))
- c_room_name_buffer[len(c_room_name_buffer) - 1] = 0
+ _, room_name_rune_count, _ := utf8.grapheme_count(room_name)
+
+ c_room_name_buffer[1 + len(room_name) + 31 - room_name_rune_count + 1] = 0
c_room_name := strings.unsafe_string_to_cstring(string(c_room_name_buffer[:]))
screen_height, screen_width := nc.getmaxyx(app.screen)
old_state := app.state
- app.state = state
+
+ {
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
+
+ app.state = state
+ }
color := nc.COLOR_PAIR(3)
if old_state != .Main && state == .Main {
- app.room_list_window = nc.newwin(screen_height - 2, 30, 0, 0)
- app.room_window = nc.newwin(screen_height - 2, screen_width - 30, 0, 30)
+ app.room_list_window = nc.newwin(screen_height - 2, 34, 0, 0)
+ app.room_window = nc.newwin(screen_height - 2, screen_width - 34, 0, 34)
app_update_room_list_window(app)
}
}
+handle_getch :: proc(app: ^App, buffer_len, cursor_pos: ^int) -> (Maybe(int), bool) {
+ char := int(nc.wgetch(app.input_window))
+
+ switch char {
+ case '\n':
+ return nil, false
+
+ case nc.KEY_BACKSPACE:
+ if cursor_pos^ != 0 {
+ buffer_len^ -= 1
+ cursor_pos^ -=1
+ }
+
+ return nil, true
+
+ case nc.KEY_LEFT:
+ if cursor_pos^ != 0 {
+ cursor_pos^ -= 1
+ }
+
+ return nil, true
+
+ case nc.KEY_RIGHT:
+ if cursor_pos^ != buffer_len^ {
+ cursor_pos^ += 1
+ }
+
+ return nil, true
+
+ case 0o400..=0o777:
+ return nil, true
+
+ case:
+ return char, true
+ }
+}
+
app_get_input :: proc(
app: ^App,
hidden := false,
allow_empty := false,
- only_command := false,
allocator := context.allocator,
) -> string {
context.allocator = allocator
nc.wrefresh(app.input_window)
buffer: [256]u8
-
- buffer_len: u32
- cursor_pos: u32
+ buffer_len, cursor_pos: int
screen_height, screen_width := nc.getmaxyx(app.screen)
nc.curs_set(1)
- input_loop: for {
- char := nc.wgetch(app.input_window)
+ for {
+ maybe_char, keep_going := handle_getch(app, &buffer_len, &cursor_pos)
- if only_command && (buffer_len == 0 || buffer[0] != ':') && char != ':' {
+ if !keep_going {
+ break
+ }
+
+ char, ok := maybe_char.?
+
+ if !ok {
continue
}
- switch char {
- case '\n':
- break input_loop
+ if char == 27 {
+ buffer_len = 0
+ cursor_pos = 0
- case 27:
- buffer_len = 0
- cursor_pos = 0
+ continue
+ }
- case nc.KEY_BACKSPACE:
- if cursor_pos != 0 {
- buffer_len -= 1
- cursor_pos -= 1
- }
+ buffer_len += 1
- case nc.KEY_LEFT:
- if cursor_pos != 0 {
- cursor_pos -= 1
- }
+ mem.copy(
+ raw_data(buffer[cursor_pos + 1 : buffer_len]),
+ raw_data(buffer[cursor_pos : buffer_len - 1]),
+ int(buffer_len - cursor_pos - 1)
+ )
+
+ buffer[cursor_pos] = u8(char)
+
+ cursor_pos += 1
+
+ buffer[buffer_len] = 0
+ output := string(buffer[:buffer_len + 1])
+
+ nc.wclear(app.input_window)
+
+ if !hidden || (buffer_len != 0 && buffer[0] == ':') {
+ nc.mvwprintw(app.input_window, 0, 0, strings.unsafe_string_to_cstring(output))
+ nc.wmove(app.input_window, 0, i32(cursor_pos))
+ }
- case nc.KEY_RIGHT:
- if cursor_pos != buffer_len {
- cursor_pos += 1
+ nc.wrefresh(app.input_window)
+ }
+
+ nc.curs_set(0)
+
+ nc.wclear(app.input_window)
+
+ c_input := cstring(raw_data(buffer[:]))
+
+ if len(c_input) == 0 && !allow_empty {
+ continue
+ }
+
+ app_set_info_bar(app, "")
+
+ return strings.clone_from_cstring(cstring(raw_data(buffer[:])))
+ }
+}
+
+Event :: enum {
+ up,
+ down,
+ select,
+ escape,
+}
+
+Command_Or_Event :: union {
+ string,
+ Event,
+}
+
+app_get_command_or_event :: proc(app: ^App, allocator := context.allocator) -> Command_Or_Event {
+ context.allocator = allocator
+
+ for {
+ nc.wclear(app.input_window)
+ nc.wrefresh(app.input_window)
+
+ buffer: [256]u8
+ buffer_len, cursor_pos: int
+
+ screen_height, screen_width := nc.getmaxyx(app.screen)
+
+ nc.curs_set(1)
+
+ for {
+ maybe_char, keep_going := handle_getch(app, &buffer_len, &cursor_pos)
+
+ if !keep_going {
+ break
+ }
+
+ char, ok := maybe_char.?
+
+ if ok {
+ if buffer_len == 0 {
+ switch char {
+ case 'k': return Event.up
+ case 'j': return Event.down
+ case '\n': return Event.select
+ case 27: return Event.escape
}
+ }
- case 0o400..=0o777:
+ if char == 27 {
+ buffer_len = 0
+ cursor_pos = 0
- case:
- buffer_len += 1
+ continue
+ }
- mem.copy(
- raw_data(buffer[cursor_pos + 1 : buffer_len]),
- raw_data(buffer[cursor_pos : buffer_len - 1]),
- int(buffer_len - cursor_pos - 1)
- )
+ if (buffer_len == 0 && char != ':') && buffer[0] != ':' {
+ continue
+ }
- buffer[cursor_pos] = u8(char)
+ buffer_len += 1
- cursor_pos += 1
+ mem.copy(
+ raw_data(buffer[cursor_pos + 1 : buffer_len]),
+ raw_data(buffer[cursor_pos : buffer_len - 1]),
+ int(buffer_len - cursor_pos - 1)
+ )
+
+ buffer[cursor_pos] = u8(char)
+
+ cursor_pos += 1
}
buffer[buffer_len] = 0
+
output := string(buffer[:buffer_len + 1])
nc.wclear(app.input_window)
- if !hidden || (buffer_len != 0 && buffer[0] == ':') {
- nc.mvwprintw(app.input_window, 0, 0, strings.unsafe_string_to_cstring(output))
- nc.wmove(app.input_window, 0, i32(cursor_pos))
- }
+ nc.mvwprintw(app.input_window, 0, 0, strings.unsafe_string_to_cstring(output))
+ nc.wmove(app.input_window, 0, i32(cursor_pos))
nc.wrefresh(app.input_window)
}
c_input := cstring(raw_data(buffer[:]))
- if len(c_input) == 0 && !allow_empty {
+ if len(c_input) == 0 {
continue
}
app.seed_phrase_checksum = entropy_hash[0] & 0b1111
}
+app_get_room_index :: proc(app: ^App, name: string) -> Maybe(int) {
+ for &room, i in app.profile.rooms {
+ if room_get_name(&room) != name {
+ continue
+ }
+
+ return i
+ }
+
+ return nil
+}
+
app_add_room :: proc(app: ^App, room: Room) -> bool {
room := room
}
app_remove_room :: proc(app: ^App, name: string) -> bool {
- for &room, i in app.profile.rooms {
- if room_get_name(&room) != name {
- continue
- }
+ room_index, ok := app_get_room_index(app, name).?
- ordered_remove(&app.profile.rooms, i)
+ if !ok {
+ return false
+ }
- profile_update(app.profile, app)
- app_update_room_list_window(app)
+ ordered_remove(&app.profile.rooms, room_index)
+ profile_update(app.profile, app)
- return true
+ if app.selected_room >= len(app.profile.rooms) {
+ app.selected_room = len(app.profile.rooms) - 1
}
- return false
+ app_update_room_list_window(app)
+
+ return true
}
handle_state :: proc(app: ^App) {
time.sleep(1_000_000)
free_all(context.temp_allocator)
}
-
- #partial switch app.state {
- case .Main:
- {
- sync.mutex_lock(&app.mutex)
- defer sync.mutex_unlock(&app.mutex)
-
- if !app.connecting && app.connection_thread != nil {
- connection_thread := app.connection_thread.?
-
- thread.destroy(connection_thread)
- app.connection_thread = nil
- }
- }
-
-
- }
}
}
app_set_state(app, .Connect_To_Host)
}
-handle_connection_to_host :: proc(app: ^App) {
- {
- sync.mutex_lock(&app.mutex)
- defer sync.mutex_unlock(&app.mutex)
-
- app.connecting = true
- }
-
- defer {
- sync.mutex_lock(&app.mutex)
- defer sync.mutex_unlock(&app.mutex)
-
- app.connecting = false
- }
-
- app_set_info_bar(app, "Connecting to host...")
+state_connect_to_host :: proc(app: ^App) {
+ app_set_info_bar(app, "Connecting to host..")
host_endpoint: net.Endpoint
if err != nil {
log.errorf("Failed to resolve host with error: %v", err)
+ app_set_info_bar(app, "Connection failed.")
app_set_state(app, .Invalid_Host)
+
return
}
}
if err != nil {
log.errorf("Failed to connect to host with error: %v", err)
+ app_set_info_bar(app, "Connection failed.")
app_set_state(app, .Invalid_Host)
+
return
}
app.host = host
}
-}
-
-state_connect_to_host :: proc(app: ^App) {
- app_set_info_bar(app, "Connecting to host..")
-
- {
- sync.mutex_lock(&app.mutex)
- defer sync.mutex_unlock(&app.mutex)
-
- app.connection_thread = thread.create_and_start_with_poly_data(app, handle_connection_to_host, context)
- }
app_set_state(app, .Main)
}
app,
{
"Failed to connect to host.",
- "Enter either a new host,",
+ "Either enter a new host,",
"or :retry."
}
)
app.profile.host = input
}
+ profile_update(app.profile, app)
+
app_set_state(app, .Connect_To_Host)
}
state_main :: proc(app: ^App) {
- input := app_get_input(app, only_command = true)
+ command_or_event := app_get_command_or_event(app)
- if err, ok := handle_command(app, input).?; ok {
- if err != .Not_A_Command {
+ defer {
+ #partial switch thing in command_or_event {
+ case string: delete(thing)
+ }
+ }
+
+ switch thing in command_or_event{
+ case string:
+ command := thing
+
+ if err, ok := handle_command(app, command).?; ok {
+ if err != .Not_A_Command {
+ return
+ }
+ } else {
return
}
- } else {
- delete(input)
- return
+
+ case Event:
+ event := thing
+
+ switch event {
+ case Event.up:
+ if app.selected_room != 0 {
+ app.selected_room -= 1
+ app_update_room_list_window(app)
+ }
+
+ case Event.down:
+ if app.selected_room + 1 != len(app.profile.rooms) {
+ app.selected_room += 1
+ app_update_room_list_window(app)
+ }
+
+ case Event.select:
+ case Event.escape:
+ }
}
}