├── favicon.ico ├── images └── a3wm.gif ├── v.mod ├── .editorconfig ├── .gitattributes ├── src ├── core │ ├── debug.v │ ├── state_workarea.v │ ├── state_debug.v │ ├── state_grid.v │ ├── state.v │ ├── state_render.v │ ├── state_get.v │ ├── model.v │ ├── state_window.v │ └── grid.v ├── main.v ├── app │ ├── utils.v │ ├── unsafe_utils.v │ ├── hook.v │ ├── callbacks.v │ ├── proc.v │ ├── hotkeys_callback.v │ ├── entrypoint.v │ ├── hotkeys.v │ └── functions.v └── winapi │ ├── color.v │ ├── virtual_keys.v │ └── winapi.v ├── .gitignore ├── readme.md └── .v-analyzer └── config.toml /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andridus/a3wm/HEAD/favicon.ico -------------------------------------------------------------------------------- /images/a3wm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andridus/a3wm/HEAD/images/a3wm.gif -------------------------------------------------------------------------------- /v.mod: -------------------------------------------------------------------------------- 1 | Module { 2 | name: 'a3wm' 3 | description: '' 4 | version: '0.1.0' 5 | license: 'MIT' 6 | dependencies: [] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | [*.v] 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.bat eol=crlf 3 | 4 | **/*.v linguist-language=V 5 | **/*.vv linguist-language=V 6 | **/*.vsh linguist-language=V 7 | **/v.mod linguist-language=V 8 | -------------------------------------------------------------------------------- /src/core/debug.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | import time 4 | #include 5 | pub fn clear_screen() { 6 | unsafe { C.system('cls'.str) } 7 | } 8 | pub fn debug(s string) { 9 | now := time.now() 10 | println('${now}: ${s}\n') 11 | } -------------------------------------------------------------------------------- /src/core/state_workarea.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | pub fn (state &State) replace_workarea(workarea Workarea) &Workarea { 4 | mut state0 := unsafe {&state} 5 | state0.workareas[workarea.idx] = workarea 6 | return &state0.workareas[workarea.idx] 7 | } 8 | -------------------------------------------------------------------------------- /src/main.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import app 4 | 5 | #include 6 | #include 7 | #flag -lgdi32 8 | #flag -lshell32 9 | 10 | fn main() { 11 | instance := C.GetModuleHandleA(unsafe { nil }) 12 | cmd_show := 1 13 | app.entrypoint(instance, cmd_show) 14 | } 15 | -------------------------------------------------------------------------------- /src/app/utils.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | fn clear_bytes(bts []u8) string { 4 | mut s := []u8{} 5 | for b in bts { 6 | if b != 0 { 7 | s << b 8 | } 9 | } 10 | return s.bytestr() 11 | } 12 | pub fn is_hwnd_same(hwnd C.HWND, hwnd2 C.HWND) bool { 13 | if hwnd == hwnd2 { return true } 14 | return false 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | main 3 | c3w 4 | *.exe 5 | *.exe~ 6 | *.so 7 | *.dylib 8 | *.dll 9 | 10 | # Ignore binary output folders 11 | bin/ 12 | 13 | # Ignore common editor/system specific metadata 14 | .DS_Store 15 | .idea/ 16 | .vscode/ 17 | *.iml 18 | 19 | # ENV 20 | .env 21 | 22 | # vweb and database 23 | *.db 24 | *.js 25 | a3wm.ilk 26 | a3wm.pdb 27 | -------------------------------------------------------------------------------- /src/winapi/color.v: -------------------------------------------------------------------------------- 1 | module winapi 2 | pub enum Color { 3 | scrollbar 4 | background 5 | activecaption 6 | inactivecaption 7 | menu 8 | window 9 | windowframe 10 | menutest 11 | windowtext 12 | captiontext 13 | activeborder 14 | inactiveborder 15 | appworkspace 16 | hightlight 17 | highlighttext 18 | btnface 19 | btnshadow 20 | graytext 21 | btntext 22 | inactivecaptiontext 23 | btnhighligh 24 | } -------------------------------------------------------------------------------- /src/app/unsafe_utils.v: -------------------------------------------------------------------------------- 1 | module app 2 | import builtin.wchar 3 | fn string_from_char(cstr []char, len int) string { 4 | str := unsafe { cstr[0].vstring_with_len(len)} 5 | str0 := clear_bytes(str.bytes()) 6 | return str0 7 | } 8 | 9 | fn wchar_to_string(str0 &u8) string{ 10 | str := unsafe { wchar.to_string(str0)} 11 | return str 12 | } 13 | 14 | pub fn is_nil(hwnd C.HWND) bool { 15 | if hwnd == unsafe { nil } { return true } 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /src/core/state_debug.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | pub fn (state &State) debug() { 4 | // clear_screen() 5 | dump(state.workareas) 6 | for k, g in state.render_grid_arr { 7 | println('\nworkarea: $k') 8 | for k0, g0 in g { 9 | win := unsafe { state.windows[k0] } 10 | title := win.title.substr_with_check(0,14) or { 11 | win.title.substr(0,win.title.len) 12 | } 13 | s := '{L: ${g0[0]} T: ${g0[1]} W: ${g0[2]} H: ${g0[3]}}' 14 | println('\t${title} ${s}') 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/core/state_grid.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | pub fn (state &State) replace_grid(grid Grid) &Grid { 4 | mut state0 := unsafe {&state} 5 | state0.grids[grid.idx] = grid 6 | return &state0.grids[grid.idx] 7 | } 8 | pub fn (state &State) add_grid(grid Grid) &Grid { 9 | mut state0 := unsafe {&state} 10 | idx := state0.grids.len 11 | state0.grids << Grid{...grid, idx: idx} 12 | state0.grids_ptr[grid.uuid] = idx 13 | return &state0.grids[idx] 14 | } 15 | 16 | 17 | pub fn (state &State) toggle_grid_direction() { 18 | // get hwnd by mouse position 19 | cursor_point := C.POINT{} 20 | if C.GetCursorPos(&cursor_point) == 1 { 21 | hwnd := state.get_window_by_mouse_position(cursor_point) or { 22 | return 23 | } 24 | mut grid := state.get_grid_by_hwnd(hwnd.str()) or { return } 25 | grid.toggle_direction() 26 | state.replace_grid(grid) 27 | state.update_render_grid() 28 | } 29 | } -------------------------------------------------------------------------------- /src/app/hook.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | import core 4 | 5 | fn window_hook(ncode int, wparam C.WPARAM, hwnd C.HWND, state &core.State) C.HHOOK { 6 | match int(wparam) { 7 | C.EVENT_SYSTEM_MINIMIZESTART { 8 | state.inactivate_window(hwnd) 9 | state.update_render_grid() 10 | state.render_grid() 11 | } 12 | C.EVENT_SYSTEM_MINIMIZEEND { 13 | state.activate_window(hwnd) 14 | state.update_render_grid() 15 | state.render_grid() 16 | } 17 | C.EVENT_SYSTEM_MOVESIZESTART { 18 | rect := C.RECT{} 19 | C.GetWindowRect(hwnd, &rect) 20 | state.start_window_resizing(hwnd, rect) 21 | // render_grid(state) 22 | } 23 | C.EVENT_SYSTEM_MOVESIZEEND { 24 | rect := C.RECT{} 25 | C.GetWindowRect(hwnd, &rect) 26 | state.end_window_resizing(hwnd, rect) 27 | state.render_grid() 28 | } 29 | else {} 30 | } 31 | return C.CallNextHookEx(unsafe { nil }, ncode, wparam, &hwnd) 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # About A3-wm 2 | Inspired by i3-wm on linux window management, but using windows 11. I decided to implement a similar functionality on windows using the language V and win32 windows APIs. 3 | 4 | It still has a lot of bugs, but we'll keep moving forward. 5 | 6 | ## To do 7 | - [x] multiple monitors 8 | - [x] adjust window side by side horizontally 9 | - [x] swap window position with other window 10 | - [x] attach new window to current grid horizontally 11 | - [x] adjust window side by side vertically 12 | - [x] add system tray 13 | - [] add popup options to system tray 14 | - [] add icon to system tray 15 | - [] attach new window to current grid vertically 16 | - [] add new window anywhere 17 | - [] drag and put window anywhere 18 | - [x] change grid direction 19 | - [x] set/unset window fullscreen 20 | - [] move between windows 21 | - [] create other workspaces 22 | - [] send window to other workspaces 23 | - [] add config file 24 | 25 | ## Keyboard Shortcuts 26 | ALT+t : toggle window direction (mouse pointer over) 27 | 28 | ## Example 29 | ![me](./images/a3wm.gif) 30 | 31 | 32 | 33 | ## Execute. 34 | install the Visual Studio for msvc compiler. 35 | 36 | `v -cc msvc run .` 37 | -------------------------------------------------------------------------------- /src/app/callbacks.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | import builtin.wchar 4 | import core 5 | import rand 6 | 7 | fn get_monitor_callback(wmonitor C.HMONITOR, hdc C.HDC, rect &C.RECT, state &core.State) int { 8 | mut state0 := unsafe { &state } 9 | monitor_info := C.MONITORINFOEX{ 10 | cbSize: sizeof(C.MONITORINFOEX) 11 | } 12 | C.GetMonitorInfo(wmonitor, &monitor_info) 13 | monitor_name := unsafe { 14 | wchar.to_string(monitor_info.szDevice) 15 | } 16 | monitor := core.Monitor{ 17 | name: monitor_name 18 | id: rand.uuid_v4() 19 | size: core.Rect{ 20 | top: monitor_info.rcMonitor.top 21 | left: monitor_info.rcMonitor.left 22 | width: monitor_info.rcMonitor.right - monitor_info.rcMonitor.left 23 | height: monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top 24 | } 25 | workarea: core.Rect{ 26 | top: monitor_info.rcWork.top + state0.get_topbar_size() 27 | left: monitor_info.rcWork.left 28 | width: monitor_info.rcWork.right - monitor_info.rcWork.left 29 | height: monitor_info.rcWork.bottom - monitor_info.rcWork.top - state0.get_topbar_size() 30 | } 31 | } 32 | state0.monitors[monitor.id] = monitor 33 | return 1 34 | } 35 | 36 | fn window_watcher_callback(handler C.HWND, state &core.State) int { 37 | add_win(handler, state) or { core.debug('error: ${err.str()}') } 38 | return 1 39 | } 40 | -------------------------------------------------------------------------------- /src/app/proc.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | import winapi 4 | 5 | fn window_proc(hwnd C.HWND, umsg int, wparam C.WPARAM, lparam C.HWND) C.LRESULT { 6 | mut state := C.GetWindowLongPtr(hwnd, C.GWLP_USERDATA) 7 | match umsg { 8 | C.WM_CREATE {} 9 | C.WM_DESTROY { 10 | C.PostQuitMessage(0) 11 | return C.LRESULT(0) 12 | } 13 | C.WM_PAINT { 14 | fill_color(hwnd, state) 15 | return C.LRESULT(0) 16 | } 17 | winapi.wm_tray_icon { 18 | match int(lparam) { 19 | C.WM_LBUTTONUP { 20 | toggle_disabled(state) 21 | } 22 | C.WM_RBUTTONUP { 23 | println('Quit application') 24 | C.PostQuitMessage(0) 25 | return C.LRESULT(0) 26 | } 27 | else {} 28 | } 29 | } 30 | else { 31 | if state != unsafe { nil } && umsg == state.shellhookid { 32 | match int(wparam) & 0x7fff { 33 | C.HSHELL_WINDOWCREATED { 34 | // fill_color(hwnd, state) 35 | result := add_win(C.HWND(lparam), state) or { 36 | false 37 | } 38 | if result { 39 | state.update_render_grid() 40 | state.render_grid() 41 | } 42 | } 43 | C.HSHELL_WINDOWDESTROYED { 44 | if remove_win(C.HWND(lparam), state) { 45 | state.update_render_grid() 46 | state.render_grid() 47 | } 48 | } 49 | else {} 50 | } 51 | 52 | } 53 | } 54 | } 55 | 56 | return C.DefWindowProcW(hwnd, umsg, wparam, lparam) 57 | } 58 | -------------------------------------------------------------------------------- /src/core/state.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | @[heap] 4 | pub struct State { 5 | topbar_enable bool 6 | topbar_size int = 50 7 | pub mut: 8 | instance C.HINSTANCE 9 | handler C.HWND 10 | shellhookid u32 11 | topbar_bgcolor int = 0x00D5D5D7 12 | topbar_txtcolor int = 0x00848484 13 | windows map[string]Window 14 | monitors map[string]Monitor 15 | workareas []Workarea 16 | grids []Grid 17 | 18 | grids_ptr map[string]int 19 | workareas_ptr map[string]int 20 | windows_grid map[string]string 21 | grid_workarea map[string]string 22 | window_workarea map[string]string 23 | 24 | 25 | render_grid_arr map[string]map[string][]int 26 | current_window ?C.HWND 27 | disabled bool 28 | next_window_position WindowPosition = .horizontal 29 | } 30 | pub fn (state &State) get_show_window(cmd_show int) int { 31 | if state.topbar_enable { 32 | return cmd_show 33 | } else { 34 | return C.SW_HIDE 35 | } 36 | 37 | } 38 | pub fn (state &State) get_window_attrs() int { 39 | if state.topbar_enable { 40 | return C.WS_EX_TOPMOST 41 | } else { 42 | return 0 43 | } 44 | 45 | } 46 | pub fn (state &State) get_topbar_size() int { 47 | if state.topbar_enable { 48 | return state.topbar_size 49 | } else { 50 | return 0 51 | } 52 | } 53 | pub fn (state &State) toggle_disabled() { 54 | mut state0 := unsafe {&state} 55 | state0.disabled = !state0.disabled 56 | } 57 | -------------------------------------------------------------------------------- /src/app/hotkeys_callback.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | import core 4 | 5 | fn callback_set_next_window_to_horizontal(state &core.State) { 6 | state.set_next_window_to(.horizontal) 7 | core.debug('ALT+h: set next direction to horizontal') 8 | } 9 | 10 | fn callback_set_next_window_to_vertical(state &core.State) { 11 | state.set_next_window_to(.vertical) 12 | core.debug('ALT+v: set next direction to vertical') 13 | } 14 | fn callback_toggle_grid_direction(state &core.State) { 15 | state.toggle_grid_direction() 16 | state.render_grid() 17 | core.debug('ALT+t: toggle direction') 18 | } 19 | fn callback_fullscreen_window(state &core.State) { 20 | state.set_window_to_fullscreen() 21 | state.render_grid() 22 | core.debug('ALT+f: fullscreen window') 23 | } 24 | fn callback_move_to_left_window(state &core.State) { 25 | // state.set_active_left_window() // TODO 26 | core.debug('ALT+LEFT: move to left window') 27 | } 28 | fn callback_move_to_top_window(state &core.State) { 29 | // state.set_active_top_window() // TODO 30 | core.debug('ALT+UP: move to window on top') 31 | } 32 | fn callback_move_to_bottom_window(state &core.State) { 33 | // state.set_active_left_window() // TODO 34 | core.debug('ALT+DOWN: move to window on right') 35 | } 36 | fn callback_move_to_right_window(state &core.State) { 37 | // state.set_active_right_window() // TODO 38 | core.debug('ALT+RIGHT: move to window bellow ') 39 | } 40 | fn callback_reset_a3wm(state &core.State) { 41 | // state.set_active_right_window() // TODO 42 | state.setup_state(state.handler, get_monitor_callback, window_watcher_callback) 43 | state.render_grid() 44 | core.debug('CTRL+SHIFT+r: reset a3wm') 45 | } 46 | fn callback_disable_a3wm(state &core.State) { 47 | // state.set_active_right_window() // TODO 48 | toggle_disabled(state) 49 | } -------------------------------------------------------------------------------- /src/winapi/virtual_keys.v: -------------------------------------------------------------------------------- 1 | module winapi 2 | 3 | pub enum Key { 4 | key_left = 0x25 5 | key_up = 0x26 6 | key_right = 0x27 7 | key_down = 0x28 8 | key_0 = 0x30 9 | key_1 10 | key_2 11 | key_3 12 | key_4 13 | key_5 14 | key_6 15 | key_7 16 | key_8 17 | key_9 18 | key_a = 0x41 19 | key_b 20 | key_c 21 | key_d 22 | key_e 23 | key_f 24 | key_g 25 | key_h 26 | key_i 27 | key_j 28 | key_k 29 | key_l 30 | key_m 31 | key_n 32 | key_o 33 | key_p 34 | key_q 35 | key_r 36 | key_s 37 | key_t 38 | key_u 39 | key_v 40 | key_w 41 | key_x 42 | key_y 43 | key_z 44 | mod_alt = 0x0001 45 | mod_control = 0x0002 46 | mod_shift = 0x0003 47 | mod_alt_shift = 0x0005 48 | mod_control_shift = 0x0006 49 | mod_win = 0x0008 50 | mod_norepeat = 0x4000 51 | 52 | } 53 | 54 | fn (k Key) str() string { 55 | return match k { 56 | .key_up { 'UP' } 57 | .key_down { 'DOWN' } 58 | .key_left { 'LEFT' } 59 | .key_right { 'RIGHT' } 60 | .key_0 { '0' } 61 | .key_1 { '1' } 62 | .key_2 { '2' } 63 | .key_3 { '3' } 64 | .key_4 { '4' } 65 | .key_5 { '5' } 66 | .key_6 { '6' } 67 | .key_7 { '7' } 68 | .key_8 { '8' } 69 | .key_9 { '9' } 70 | .key_a { 'a' } 71 | .key_b { 'b' } 72 | .key_c { 'c' } 73 | .key_d { 'd' } 74 | .key_e { 'e' } 75 | .key_f { 'f' } 76 | .key_g { 'g' } 77 | .key_h { 'h' } 78 | .key_i { 'i' } 79 | .key_j { 'j' } 80 | .key_k { 'k' } 81 | .key_l { 'l' } 82 | .key_m { 'm' } 83 | .key_n { 'n' } 84 | .key_o { 'o' } 85 | .key_p { 'p' } 86 | .key_q { 'q' } 87 | .key_r { 'r' } 88 | .key_s { 's' } 89 | .key_t { 't' } 90 | .key_u { 'u' } 91 | .key_v { 'v' } 92 | .key_w { 'w' } 93 | .key_x { 'x' } 94 | .key_y { 'y' } 95 | .key_z { 'z' } 96 | .mod_alt { 'ALT' } 97 | .mod_control { 'CTRL' } 98 | .mod_shift { 'SHIFT' } 99 | .mod_win { 'SUPER' } 100 | .mod_norepeat { 'NOREPEAT' } 101 | .mod_control_shift { 'CTRL+SHIFT' } 102 | .mod_alt_shift { 'ALT+SHIFT' } 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/core/state_render.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | pub fn (state &State) setup_topbar() { 4 | mut monitor_rect := Rect{} 5 | for _, monitor in state.monitors { 6 | if monitor.size.left == 0 { 7 | monitor_rect = monitor.size 8 | } 9 | } 10 | C.MoveWindow(state.handler, monitor_rect.left, monitor_rect.top, monitor_rect.width, 50, 1) 11 | // RedrawWindow(hWnd, NULL, 0, RDW_INVALIDATE | RDW_ERASE); 12 | 13 | C.UpdateWindow(state.handler) 14 | } 15 | 16 | fn (state &State) clear_state() { 17 | mut state0 := unsafe { state } 18 | state0.windows.clear() 19 | state0.monitors.clear() 20 | state0.workareas.clear() 21 | state0.grids.clear() 22 | state0.workareas_ptr.clear() 23 | state0.windows_grid.clear() 24 | state0.grid_workarea.clear() 25 | state0.window_workarea.clear() 26 | state0.render_grid_arr.clear() 27 | } 28 | pub fn (state &State) setup_state(hwnd C.HWND, monitor_callback fn (C.HMONITOR, C.HDC, &C.RECT, &State) int, window_callback fn (C.HWND, &State) int ) { 29 | // state.clear_state() 30 | C.EnumDisplayMonitors(unsafe { nil }, unsafe { nil }, monitor_callback, state) 31 | C.EnumWindows(window_callback, state) 32 | state.update_render_grid() 33 | state.render_grid() 34 | } 35 | 36 | pub fn (state &State) update_render_grid() { 37 | if state.disabled { return } 38 | mut state0 := unsafe {&state} 39 | 40 | for _, ws in state.workareas { 41 | mut rets := map[string][]int{} 42 | if ws.fullscreen.valid { 43 | rets[ws.fullscreen.hwnd ] = ws.fullscreen.rect 44 | }else{ 45 | grid := state.get_grid_by_uuid(ws.grid_idx) 46 | 47 | rects0 := grid.get_deep_rects(state) or { 48 | debug(err.msg()) 49 | return } 50 | for hwnd, r in rects0 { 51 | rets[hwnd] = r 52 | } 53 | } 54 | state0.render_grid_arr[ws.uuid] = rets.move() 55 | } 56 | } 57 | pub fn (state &State) render_grid() { 58 | if state.disabled { return } 59 | // state.debug() 60 | for _, grid in state.render_grid_arr { 61 | for hwnd_str, rect in grid { 62 | ptr := unsafe {state.windows[hwnd_str].ptr} 63 | left := rect[0] 64 | top := rect[1] 65 | width := rect[2] 66 | height := rect[3] 67 | C.MoveWindow(ptr, left, top, width, height, 1) 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/app/entrypoint.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | import builtin.wchar 4 | import winapi 5 | import core 6 | 7 | pub fn entrypoint(instance C.HINSTANCE, cmd_show int) int { 8 | mut state := &core.State{instance: instance} 9 | 10 | register_hotkeys() 11 | // window hook to get any resized window 12 | g_hook := C.SetWinEventHook(C.EVENT_MIN, C.EVENT_MAX, unsafe { nil }, fn [state] (ncode int, wparam C.WPARAM, hwnd C.HWND) C.HHOOK { 13 | return window_hook(ncode, wparam, hwnd, state) 14 | }, 0, 0, C.WINEVENT_OUTOFCONTEXT) 15 | 16 | classname := wchar.from_string('a3class') 17 | title := wchar.from_string('A3 Window Manager') 18 | shellhook := wchar.from_string('SHELLHOOK') 19 | wc := winapi.WindowClass{ 20 | lpfnWndProc: window_proc 21 | hInstance: instance 22 | lpszClassName: classname 23 | hIcon: C.LoadIconW(unsafe { nil }, 32512) 24 | hCursor: C.LoadCursorW(unsafe { nil }, 32512) 25 | } 26 | C.RegisterClass(&wc) 27 | 28 | hwnd := C.CreateWindowEx(state.get_window_attrs(), classname, title, C.WS_POPUP , 0, 0, 0, state.get_topbar_size(), unsafe { nil }, 29 | 0x0, instance, 0x0) 30 | 31 | if hwnd == unsafe { nil } { 32 | return 0 33 | } 34 | 35 | C.ShowWindow(hwnd, state.get_show_window(cmd_show)) 36 | C.RegisterShellHookWindow(hwnd) 37 | state.shellhookid = C.RegisterWindowMessageW(shellhook) 38 | C.SetWindowLongPtr(hwnd, C.GWLP_USERDATA, state) 39 | state.handler = hwnd 40 | 41 | state.setup_state(hwnd, get_monitor_callback, window_watcher_callback) 42 | state.setup_topbar() 43 | 44 | nid := create_a_notify_icon(hwnd) 45 | msg := C.MSG{} 46 | for C.GetMessage(&msg, unsafe { nil }, 0, 0) > 0 { 47 | map_hotkeys(&msg, state) 48 | C.TranslateMessage(&msg) 49 | C.DispatchMessage(&msg) 50 | } 51 | 52 | C.UnhookWinEvent(g_hook) 53 | C.Shell_NotifyIconW(C.NIM_DELETE, nid) 54 | return 0 55 | } 56 | 57 | fn create_a_notify_icon(hwnd C.HWND) &C.NOTIFYICONDATAA { 58 | 59 | id_trap_app_icon := 5000 60 | nid := &C.NOTIFYICONDATAA{ 61 | szTip: c'A3wm Configuration' 62 | cbSize: sizeof(C.NOTIFYICONDATAA) 63 | hWnd: hwnd 64 | uID: id_trap_app_icon 65 | uFlags: C.NIF_ICON | C.NIF_MESSAGE | C.NIF_TIP 66 | uCallbackMessage: winapi.wm_tray_icon 67 | hIcon: C.LoadIconW(unsafe {nil}, C.IDI_APPLICATION) 68 | } 69 | C.Shell_NotifyIconA(C.NIM_ADD, nid) 70 | return nid 71 | } -------------------------------------------------------------------------------- /src/app/hotkeys.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | import winapi 4 | import core 5 | 6 | fn register_hotkeys() { 7 | // Register Hot Key 8 | // Follow the https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes 9 | // ALT + v : next window on vertical disposition 10 | // ALT + h : next window on horizontal disposition 11 | register_one_hotkey(1, winapi.Key.mod_alt , winapi.Key.key_h, 'set direction to HORIZONTAL') 12 | register_one_hotkey(2, winapi.Key.mod_alt , winapi.Key.key_v, 'set direction to VERTICAL') 13 | register_one_hotkey(3, winapi.Key.mod_alt , winapi.Key.key_t, 'toggle direction for selected grid') 14 | register_one_hotkey(4, winapi.Key.mod_alt , winapi.Key.key_f, 'set fullscreen for active window') 15 | register_one_hotkey(5, winapi.Key.mod_alt , winapi.Key.key_left, 'move to left window') 16 | register_one_hotkey(6, winapi.Key.mod_alt , winapi.Key.key_up, 'move to top window') 17 | register_one_hotkey(7, winapi.Key.mod_alt , winapi.Key.key_right, 'move to right window') 18 | register_one_hotkey(8, winapi.Key.mod_alt , winapi.Key.key_down, 'move to bottom window') 19 | register_one_hotkey(9, winapi.Key.mod_control_shift , winapi.Key.key_r, 'reset A3wm') 20 | register_one_hotkey(10, winapi.Key.mod_control_shift , winapi.Key.key_d, 'enable/disable A3wm') 21 | 22 | } 23 | 24 | fn map_hotkeys(msg &C.MSG, state &core.State) { 25 | if msg.message == C.WM_HOTKEY { 26 | match int(msg.wParam) { 27 | 1 { callback_set_next_window_to_horizontal(state) } 28 | 2 { callback_set_next_window_to_vertical(state) } 29 | 3 { callback_toggle_grid_direction(state) } 30 | 4 { callback_fullscreen_window(state) } 31 | 5 { callback_move_to_left_window(state) } 32 | 6 { callback_move_to_top_window(state) } 33 | 7 { callback_move_to_right_window(state) } 34 | 8 { callback_move_to_bottom_window(state) } 35 | 9 { callback_reset_a3wm(state) } 36 | 10 { callback_disable_a3wm(state) } 37 | 38 | else {} 39 | } 40 | } 41 | } 42 | 43 | fn register_one_hotkey(code int, modifier winapi.Key, key winapi.Key, description string) { 44 | if C.RegisterHotKey(unsafe { nil }, code, u32(modifier), u32(key)) == 1 { 45 | core.debug('Hotkey \'${modifier}+${key}\' registered for `${description}`') 46 | } else { 47 | core.debug('ERROR: Hotkey \'${modifier}+${key}\' was not possible') 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/core/state_get.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | pub fn (state &State) get_workarea_by_grid(grid_uuid string) !&Workarea { 4 | wa_uuid := state.grid_workarea[grid_uuid] 5 | if grid_uuid != '' { 6 | return state.get_workarea_by_uuid(wa_uuid) 7 | } else { 8 | return error('not found workarea by grid uuid') 9 | } 10 | } 11 | pub fn (state &State) get_workarea_by_uuid(uuid string) &Workarea { 12 | wa_idx := state.workareas_ptr[uuid] 13 | wa := &state.workareas[wa_idx] 14 | return wa 15 | } 16 | pub fn (state &State) get_grid_by_hwnd(hwnd string) !&Grid { 17 | grid_uuid := state.windows_grid[hwnd] 18 | if grid_uuid != '' { 19 | return state.get_grid_by_uuid(grid_uuid) 20 | } else { 21 | return error('not found grid by hwnd') 22 | } 23 | } 24 | pub fn (state &State) get_grid_by_uuid(uuid string) &Grid { 25 | grid_idx := state.grids_ptr[uuid] 26 | grid := &state.grids[grid_idx] 27 | return grid 28 | } 29 | 30 | pub fn (state &State) get_monitor_by_id(id string) Monitor { 31 | return state.monitors[id] 32 | } 33 | 34 | pub fn (state &State) get_window_by_mouse_position(point C.POINT) !C.HWND { 35 | for _, wa in state.workareas { 36 | grid := state.get_grid_by_uuid(wa.grid_idx) 37 | finded, hwnd := state.get_window_by_mouse_position_deep_grid(grid, point) 38 | if finded { 39 | return unsafe {state.windows[hwnd].ptr} 40 | } 41 | } 42 | return error('not found window') 43 | } 44 | fn (state &State) get_window_by_mouse_position_deep_grid(grid &Grid, point C.POINT) (bool, string) { 45 | if grid.rect.has_point(point) { 46 | if grid.all_actives() { 47 | rect_elem1 := grid.get_elem_rect_for1() 48 | rect_elem2 := grid.get_elem_rect_for2() 49 | if rect_elem1.has_point(point) { 50 | return state.get_window_by_mouse_position_deep(grid.elem1, point) 51 | } else if rect_elem2.has_point(point) { 52 | return state.get_window_by_mouse_position_deep(grid.elem2, point) 53 | } 54 | } else { 55 | if grid.active1 { 56 | return state.get_window_by_mouse_position_deep(grid.elem1, point) 57 | } 58 | else if grid.active2 { 59 | return state.get_window_by_mouse_position_deep(grid.elem2, point) 60 | } 61 | } 62 | } 63 | return false, '' 64 | } 65 | fn (state &State) get_window_by_mouse_position_deep(gw GridWindow, point C.POINT) (bool, string ) { 66 | match gw { 67 | GridAddress { 68 | grid := state.get_grid_by_uuid(gw) 69 | return state.get_window_by_mouse_position_deep_grid(grid, point ) 70 | } 71 | WindowAddress { 72 | return true, gw 73 | } 74 | None { 75 | return false, '' 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/core/model.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | pub const monitor_display_1 = '\\\\.\\DISPLAY1' 4 | pub const monitor_display_2 = '\\\\.\\DISPLAY2' 5 | pub const classes = ['Windows.UI.Core.CoreWindow', 'ApplicationFrameWindow'] 6 | 7 | pub enum Action { 8 | swap_window 9 | drag_axis 10 | nothing 11 | } 12 | 13 | pub enum WindowPosition { 14 | horizontal 15 | vertical 16 | } 17 | 18 | pub struct Rect { 19 | pub: 20 | top int 21 | left int 22 | width int 23 | height int 24 | valid bool = true 25 | } 26 | 27 | 28 | pub fn (r Rect) str() string { 29 | if r.valid { 30 | return '{L: ${r.left}, T: ${r.top}, W: ${r.width}, H: ${r.height}}' 31 | } else { 32 | return 'invalid rect' 33 | } 34 | } 35 | 36 | pub fn (r Rect) to_list_int() []int { 37 | return [r.left, r.top, r.width, r.height] 38 | } 39 | 40 | pub fn (r Rect) has_point(p C.POINT) bool { 41 | if p.x >= r.left && p.x <= r.left + r.width && p.y >= r.top && p.y <= r.top + r.height { return true } else { return false } 42 | } 43 | 44 | pub struct Monitor { 45 | pub: 46 | id string 47 | name string 48 | size Rect 49 | workarea Rect 50 | } 51 | 52 | pub struct FullscreenWindow { 53 | hwnd string 54 | rect []int 55 | valid bool 56 | } 57 | 58 | pub struct Workarea { 59 | pub: 60 | uuid string 61 | idx int 62 | name string 63 | monitor string 64 | active bool 65 | pub mut: 66 | windows []&Window 67 | fullscreen FullscreenWindow 68 | grid_idx string 69 | } 70 | 71 | pub fn (w &Workarea) set_grid(grid_id string) { 72 | mut w0 := unsafe { &w} 73 | w0.grid_idx = grid_id 74 | } 75 | pub fn (w &Workarea) set_fullscreen(grid &Grid, window_address string, monitor Monitor) { 76 | mut w0 := unsafe { &w} 77 | rect := monitor.workarea 78 | w0.fullscreen = FullscreenWindow{valid: true, hwnd: window_address, rect: [rect.left, rect.top, rect.width, rect.height]} 79 | } 80 | pub fn (w &Workarea) unset_fullscreen() { 81 | mut w0 := unsafe { &w} 82 | w0.fullscreen = FullscreenWindow{valid: false} 83 | } 84 | 85 | 86 | pub struct Window { 87 | pub: 88 | ptr C.HWND 89 | title string 90 | classname string 91 | pub mut: 92 | position int 93 | active bool 94 | } 95 | 96 | pub fn (windows []Window) count_active() int { 97 | mut i := 0 98 | for w in windows { 99 | if w.active { 100 | i++ 101 | } 102 | } 103 | return i 104 | } 105 | 106 | pub fn (windows []Window) get_actives() []Window { 107 | mut wins := []Window{} 108 | for w in windows { 109 | if w.active { 110 | wins << w 111 | } 112 | } 113 | wins.sort(a.position < b.position) 114 | return wins 115 | } 116 | -------------------------------------------------------------------------------- /src/core/state_window.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | pub fn (state &State) add_window(window Window) { 4 | mut state0 := unsafe {&state} 5 | state0.windows[window.ptr.str()] = window 6 | } 7 | pub fn (state &State) update_windows_grid_reference_from_grid(grid &Grid) { 8 | mut state0 := unsafe {&state} 9 | elem1 := grid.elem1.get_window_address() or { return } 10 | state0.windows_grid[elem1] = grid.uuid 11 | elem2 := grid.elem2.get_window_address() or { return } 12 | state0.windows_grid[elem2] = grid.uuid 13 | } 14 | pub fn (state &State) add_windows_reference(window Window, grid Grid, workarea &Workarea) { 15 | mut state0 := unsafe { &state } 16 | window_ptr := window.ptr.str() 17 | state0.windows[window_ptr] = window 18 | state0.window_workarea[window_ptr] = workarea.uuid 19 | state0.grid_workarea[grid.uuid] = workarea.uuid 20 | state0.workareas_ptr[workarea.uuid] = workarea.idx 21 | state0.grids_ptr[grid.uuid] = grid.idx 22 | state0.windows_grid[window_ptr] = grid.uuid 23 | } 24 | pub fn (state &State) set_next_window_to(pos WindowPosition) { 25 | mut state0 := *state 26 | state0.next_window_position = pos 27 | } 28 | pub fn (state &State) remove_window(hwnd string) bool { 29 | mut state0 := unsafe {&state} 30 | state0.windows_grid.delete(hwnd) 31 | state0.windows.delete(hwnd) 32 | return true 33 | } 34 | 35 | pub fn (state &State) activate_window(hwnd C.HWND) { 36 | mut state0 := unsafe {&state} 37 | for _, mut wa in state0.workareas { 38 | for mut w in wa.windows { 39 | if w.ptr == hwnd { 40 | w.active = true 41 | } 42 | } 43 | } 44 | } 45 | 46 | pub fn (state &State) inactivate_window(hwnd C.HWND) { 47 | mut state0 := unsafe {&state} 48 | for _, mut wa in state0.workareas { 49 | for mut w in wa.windows { 50 | if w.ptr == hwnd { 51 | w.active = false 52 | } 53 | } 54 | } 55 | } 56 | 57 | pub fn (state &State) swap_window_position(current C.HWND, target C.HWND) { 58 | if state.grids.len == 0 { return } 59 | 60 | mut gd1 := state.get_grid_by_hwnd(current.str()) or { return } 61 | mut gd2 := state.get_grid_by_hwnd(target.str()) or { return } 62 | pos1 := gd1.which_elem_position(current.str()) 63 | pos2 := gd2.which_elem_position(target.str()) 64 | gd1.replace_window(pos1, target) 65 | gd2.replace_window(pos2, current) 66 | } 67 | 68 | pub fn (state &State) start_window_resizing(hwnd C.HWND, rect C.RECT) { 69 | // grid := state.get_grid_by_hwnd(hwnd.str()) or { return } 70 | // el := grid.which_elem_position(hwnd.str()) 71 | } 72 | pub fn (state &State) end_window_resizing(hwnd C.HWND, rect C.RECT) { 73 | if state.grids.len == 0 { return } 74 | grid := state.get_grid_by_hwnd(hwnd.str()) or { return } 75 | match grid.parse_axis(rect, hwnd, state) { 76 | .nothing { } 77 | .drag_axis { } 78 | .swap_window { 79 | cursor_point := C.POINT{} 80 | if C.GetCursorPos(&cursor_point) == 1 { 81 | ptr := state.get_window_by_mouse_position(cursor_point) or { 82 | return 83 | } 84 | state.swap_window_position(hwnd, ptr) 85 | } 86 | } 87 | } 88 | state.replace_grid(grid) 89 | state.update_render_grid() 90 | } 91 | 92 | 93 | pub fn (state &State) set_window_to_fullscreen() { 94 | hwnd :=C.GetForegroundWindow() 95 | if hwnd == 0x0 { 96 | return 97 | } 98 | mut grid := state.get_grid_by_hwnd(hwnd.str()) or { return } 99 | wa := state.get_workarea_by_grid(grid.uuid) or {return} 100 | if wa.fullscreen.valid { 101 | wa.unset_fullscreen() 102 | } else { 103 | monitor := state.monitors[wa.monitor] 104 | wa.set_fullscreen(grid, hwnd.str(), monitor) 105 | } 106 | state.replace_workarea(wa) 107 | state.update_render_grid() 108 | } -------------------------------------------------------------------------------- /.v-analyzer/config.toml: -------------------------------------------------------------------------------- 1 | # Specifies the path to the V installation directory with `v` executable. 2 | # If not set, the plugin will try to find it on its own. 3 | # Set it if you get errors like "Cannot find V standard library!". 4 | #custom_vroot = "~/v" 5 | 6 | # Specifies the path where to store the cache. 7 | # By default, it is stored in the system's cache directory. 8 | # You can set it to `./` to store the cache in the project's directory, this is useful 9 | # if you want to debug the analyzer. 10 | # Basically, you don't need to set it. 11 | #custom_cache_dir = "./" 12 | 13 | # Specifies whenever to enable semantic tokens or not. 14 | # - `full` — enables all semantic tokens. In this mode analyzer resolves all symbols 15 | # in the file to provide the most accurate highlighting. 16 | # - `syntax` — enables only syntax tokens, such tokens highlight structural elements 17 | # such as field names or import names. 18 | # The fastest option, which is always enabled when the file contains more than 1000 lines. 19 | # - `none` — disables semantic tokens. 20 | # By default, `full` for files with less than 1000 lines, `syntax` for files with more. 21 | enable_semantic_tokens = "full" 22 | 23 | # Specifies inlay hints to show. 24 | [inlay_hints] 25 | # Specifies whenever to enable inlay hints or not. 26 | # By default, they are enabled. 27 | enable = true 28 | 29 | # Specifies whenever to show type hints for ranges or not. 30 | # Example: 31 | # ``` 32 | # 0 ≤ .. < 10 33 | # ^ ^ 34 | # ``` 35 | # or: 36 | # ``` 37 | # a[0 ≤ .. < 10] 38 | # ^ ^ 39 | # ``` 40 | enable_range_hints = true 41 | 42 | # Specifies whenever to show type hints for variables or not. 43 | # Example: 44 | # ``` 45 | # name : Foo := foo() 46 | # ^^^^^ 47 | # ``` 48 | enable_type_hints = true 49 | 50 | # Specifies whenever to show hints for implicit err variables or not. 51 | # Example: 52 | # ``` 53 | # foo() or { err -> 54 | # ^^^^^^ 55 | # } 56 | # ``` 57 | enable_implicit_err_hints = true 58 | 59 | # Specifies whenever to show hints for function parameters in call or not. 60 | # Example: 61 | # ``` 62 | # fn foo(a int, b int) int {} 63 | # 64 | # foo(a: 1, b: 2) 65 | # ^^ ^^ 66 | enable_parameter_name_hints = true 67 | 68 | # Specifies whenever to show type hints for constants or not. 69 | # Example: 70 | # ``` 71 | # const foo : int = 1 72 | # ^^^^^ 73 | # ``` 74 | enable_constant_type_hints = true 75 | 76 | # Specifies whenever to show hints for enum field values or not. 77 | # Example: 78 | # ``` 79 | # enum Foo { 80 | # bar = 0 81 | # ^^^ 82 | # baz = 1 83 | # ^^^ 84 | # } 85 | # ``` 86 | enable_enum_field_value_hints = true 87 | 88 | # Specifies code lenses to show. 89 | [code_lens] 90 | # Specifies whenever to enable code lenses or not. 91 | # By default, they are enabled. 92 | enable = true 93 | 94 | # Specifies whenever to show code lenses for main function to run current directory or not. 95 | # Example: 96 | # ``` 97 | # ▶ Run 98 | # fn main() {} 99 | # ``` 100 | enable_run_lens = true 101 | 102 | # Specifies whenever to show code lenses for interface inheritors or not. 103 | # Example: 104 | # ``` 105 | # 2 implementations 106 | # interface Foo {} 107 | # ``` 108 | enable_inheritors_lens = true 109 | 110 | # Specifies whenever to show code lenses for structs implementing interfaces or not. 111 | # Example: 112 | # ``` 113 | # implemented 2 interfaces 114 | # struct Boo {} 115 | # ``` 116 | enable_super_interfaces_lens = true 117 | 118 | # Specifies whenever to show code lenses for test functions to run test or whole file or not. 119 | # Example: 120 | # ``` 121 | # ▶ Run test | all file tests 122 | # fn test_foo() {} 123 | # ``` 124 | # Note: "all file tests" is shown only for the first test function in the file. 125 | enable_run_tests_lens = true 126 | -------------------------------------------------------------------------------- /src/winapi/winapi.v: -------------------------------------------------------------------------------- 1 | module winapi 2 | 3 | import core 4 | 5 | type C.WPARAM = u32 6 | type C.LPARAM = u32 7 | 8 | @[typedef] 9 | pub struct C.NOTIFYICONDATAA{ 10 | cbSize u32 11 | hWnd C.HWND 12 | uID int 13 | uFlags int 14 | uCallbackMessage int 15 | hIcon C.HICON 16 | szInfoTitle &u8 = unsafe {nil} 17 | szTip &u8 = unsafe {nil} 18 | } 19 | 20 | @[typedef] 21 | pub struct C.RECT { 22 | top int 23 | left int 24 | right int 25 | bottom int 26 | } 27 | 28 | @[typedef] 29 | pub struct C.HICON {} 30 | 31 | @[typedef] 32 | pub struct C.HCURSOR {} 33 | 34 | @[typedef] 35 | pub struct C.HWND {} 36 | 37 | @[typedef] 38 | pub struct C.HINSTANCE {} 39 | 40 | @[typedef] 41 | struct C.PAINTSTRUCT { 42 | pub: 43 | rcPaint C.RECT 44 | } 45 | 46 | pub type PaintStruct = C.PAINTSTRUCT 47 | 48 | @[typedef] 49 | pub struct C.HHOOK {} 50 | 51 | @[typedef] 52 | pub struct C.HDC {} 53 | 54 | @[typedef] 55 | pub struct C.HBRUSH {} 56 | 57 | @[typedef] 58 | pub struct C.MSG { 59 | hwnd C.HWND 60 | message u32 61 | wParam C.WPARAM 62 | lParam C.LPARAM 63 | time u32 64 | pt C.POINT 65 | } 66 | 67 | pub type Msg = C.MSG 68 | 69 | @[typedef] 70 | pub struct C.HINSTANCE {} 71 | 72 | @[typedef] 73 | pub struct C.COLORREF {} 74 | 75 | 76 | @[typedef] 77 | pub struct C.HMONITOR {} 78 | 79 | @[typedef] 80 | pub struct C.MONITORINFOEX { 81 | szDevice &u8 = unsafe { nil } 82 | cbSize u32 83 | rcMonitor C.RECT 84 | rcWork C.RECT 85 | dwFlags u32 86 | } 87 | 88 | @[typedef] 89 | pub struct C.MONITORINFO { 90 | cbSize u32 91 | rcMonitor C.RECT 92 | rcWork C.RECT 93 | dwFlags u32 94 | } 95 | 96 | @[typedef] 97 | pub struct C.WNDCLASS { 98 | style u32 99 | lpfnWndProc fn (C.HWND, int, C.WPARAM, C.HWND) C.LRESULT 100 | hInstance C.HINSTANCE 101 | lpszClassName &u8 = unsafe { nil } 102 | hCursor C.HCURSOR 103 | hIcon C.HICON 104 | } 105 | 106 | @[typedef] 107 | pub struct C.HWINEVENTHOOK {} 108 | 109 | pub type WindowClass = C.WNDCLASS 110 | 111 | @[typedef] 112 | pub struct C.POINT { 113 | x u32 114 | y u32 115 | } 116 | 117 | pub const wm_tray_icon = C.WM_USER + 1 118 | 119 | fn C.GetCursorPos(&C.POINT) u8 120 | fn C.MessageBoxA(&u8, &u8, &u8, int) int 121 | fn C.CreateWindowExW(u8, &u8, &u8, u32, int, int, int, int, C.HWND, &u8, C.HINSTANCE, &u8) C.HWND 122 | fn C.CreateWindowEx(u8, &u8, &u8, u32, int, int, int, int, C.HWND, &u8, C.HINSTANCE, &u8) C.HWND 123 | fn C.CreateWindow(&u8, &u8, u32, int, int, int, int, C.HWND, &u8, C.HINSTANCE, &u8) C.HWND 124 | fn C.ShowWindow(C.HWND, int) 125 | fn C.RegisterClass(&C.WNDCLASS) 126 | fn C.DefWindowProcW(C.HWND, int, C.WPARAM, C.HWND) C.LRESULT 127 | fn C.PostQuitMessage(int) 128 | fn C.GetMessage(&u8, &u8, int, int) int 129 | fn C.TranslateMessage(&C.MSG) 130 | fn C.DispatchMessage(&C.MSG) 131 | fn C.BeginPaint(C.HWND, &C.PAINTSTRUCT) C.HDC 132 | fn C.EndPaint(C.HWND, &C.PAINTSTRUCT) C.HDC 133 | fn C.FillRect(C.HDC, &u8, C.HBRUSH) 134 | fn C.LoadCursorW(C.HINSTANCE, int) C.HCURSOR 135 | fn C.LoadIconW(C.HINSTANCE, int) C.HICON 136 | fn C.RegisterShellHookWindow(C.HWND) bool 137 | fn C.RegisterWindowMessageW(&u8) u32 138 | fn C.SetWindowLongPtr(C.HWND, int, &u8) 139 | fn C.SetWindowLong(C.HWND, int, int) 140 | fn C.EnumWindows(fn (C.HWND, &core.State) int, &u8) int 141 | fn C.GetParent(C.HWND) C.HWND 142 | fn C.GetWindowText(C.HWND, &u8, int) 143 | fn C.GetClassName(C.HWND, &u8, int) 144 | fn C.IsWindow(C.HWND) int 145 | fn C.IsWindowVisible(C.HWND) int 146 | fn C.GetWindowLong(C.HWND, int) int 147 | fn C.GetWindow(C.HWND, int) int 148 | fn C.GetWindowRect(C.HWND, &C.RECT) 149 | fn C.DestroyWindow(C.HWND) int 150 | fn C.GetWindowLongPtr(C.HWND, int) &core.State 151 | fn C.MoveWindow(C.HWND, int, int, int, int, int) int 152 | fn C.RegisterHotKey(C.HWND, int, u32, u32) int 153 | 154 | fn C.MonitorFromWindow(C.HWND, int) C.HMONITOR 155 | fn C.GetMonitorInfo(C.HMONITOR, &C.MONITORINFOEX) 156 | fn C.EnumDisplayMonitors(&C.HDC, &C.RECT, fn (C.HMONITOR, C.HDC, &C.RECT, &core.State) int, &u8) 157 | 158 | fn C.SetWindowsHookEx(int, fn (int, C.WPARAM, C.HWND) C.HHOOK, C.HINSTANCE, &u8) C.HHOOK 159 | fn C.SetWindowsHookExA(int, fn (int, C.WPARAM, C.HWND) C.HHOOK, C.HINSTANCE, &u8) C.HHOOK 160 | fn C.CallNextHookEx(C.HHOOK, int, C.WPARAM, C.HWND) C.HHOOK 161 | fn C.GetCurrentThreadId() &u8 162 | fn C.GetModuleHandleA(&u8) C.HINSTANCE 163 | fn C.LoadLibraryA(&u8) C.HINSTANCE 164 | fn C.UnhookWindowsHookExA(C.HHOOK) 165 | fn C.GetLastError() u32 166 | fn C.SetWinEventHook(int, int, &u8, fn (int, C.WPARAM, C.HWND) C.HHOOK, int, int, int) C.HWINEVENTHOOK 167 | fn C.UnhookWinEvent(C.HWINEVENTHOOK) 168 | // fn C.GetActiveWindow() C.HWND 169 | fn C.GetForegroundWindow() C.HWND 170 | fn C.SetActiveWindow(C.HWND) C.HWND 171 | fn C.Shell_NotifyIconW(int, &u8) int 172 | fn C.Shell_NotifyIconA(int, &u8) int 173 | fn C.SetWindowPos(C.HWND, C.HWND, int, int, int, int, u32) 174 | // fn C.RGB(int, int, int) C.COLORREF 175 | fn C.SetClassLongPtr(C.HWND, u32, &u8) 176 | fn C.SetClassLong(C.HWND, u32, &u8) 177 | fn C.CreateSolidBrush(int) C.HBRUSH 178 | fn C.GetClientRect(C.HWND, &u8) 179 | fn C.UpdateWindow(C.HWND) 180 | fn C.RedrawWindow(C.HWND, &u8, u32, u32) int 181 | fn C.TextOutA(C.HDC, int, int, &u8, int) int 182 | fn C.SetBkColor(C.HDC, int) C.COLORREF 183 | fn C.SetTextColor(C.HDC, int) C.COLORREF 184 | fn C.DrawTextA(C.HDC, &u8, int, &u8, int) int -------------------------------------------------------------------------------- /src/app/functions.v: -------------------------------------------------------------------------------- 1 | module app 2 | 3 | import core 4 | import rand 5 | import winapi 6 | import builtin.wchar 7 | 8 | fn get_monitor_by_win(hwnd C.HWND, state &core.State) !core.Monitor { 9 | monitor := C.MonitorFromWindow(hwnd, C.MONITOR_DEFAULTTONEAREST) 10 | monitor_info := C.MONITORINFOEX{ 11 | cbSize: sizeof(C.MONITORINFOEX) 12 | } 13 | C.GetMonitorInfo(monitor, &monitor_info) 14 | monitor_name := wchar_to_string(monitor_info.szDevice) 15 | for _, m in state.monitors { 16 | if monitor_name == m.name { 17 | return m 18 | } 19 | } 20 | return error("don't find monitor") 21 | } 22 | 23 | fn add_win(hwnd C.HWND, state &core.State) !bool { 24 | // check if exists window 25 | win0 := state.windows[hwnd.str()] or {core.Window{}} 26 | if win0 != core.Window{} { return false } 27 | 28 | len := 1024 29 | title_ptr := []char{len: len, cap: len} 30 | classname_ptr := []char{len: len, cap: len} 31 | 32 | if !is_nil(hwnd) && !is_hwnd_same(hwnd, state.handler) { 33 | C.GetWindowText(hwnd, title_ptr.data, len) 34 | C.GetClassName(hwnd, classname_ptr.data, len) 35 | title := string_from_char(title_ptr, len) 36 | classname := string_from_char(classname_ptr, len) 37 | if C.IsWindow(hwnd) == 1 && C.IsWindowVisible(hwnd) == 1 && title != '' { 38 | exstyle := C.GetWindowLong(hwnd, C.GWL_EXSTYLE) 39 | style := C.GetWindowLong(hwnd, C.GWL_STYLE) 40 | window_owner := C.GetWindow(hwnd, C.GW_OWNER) 41 | mut active := false 42 | if (style & C.WS_MINIMIZE) == 0 { active = true } 43 | if ((exstyle & C.WS_EX_TOOLWINDOW) == 0 && window_owner == 0) 44 | || ((exstyle & C.WS_EX_APPWINDOW) != 0 && window_owner != 0) { 45 | if classname !in core.classes { 46 | monitor := get_monitor_by_win(hwnd, state)! 47 | window := core.Window{ 48 | title: title 49 | ptr: hwnd 50 | classname: classname 51 | active: active 52 | } 53 | state.add_window(window) 54 | add_window_in_active_workarea(window, state, monitor) 55 | return true 56 | } 57 | } 58 | } 59 | } 60 | return false 61 | } 62 | 63 | fn remove_win(hwnd C.HWND, state &core.State) bool { 64 | if state.grids.len == 0 { return false} 65 | mut grid := state.get_grid_by_hwnd(hwnd.str()) or { return false} 66 | workarea := state.get_workarea_by_grid(grid.uuid) or { return false} 67 | has_grid, new_grid := grid.remove_window(hwnd, state) 68 | state.remove_window(hwnd.str()) 69 | 70 | if has_grid { 71 | workarea.set_grid(new_grid.uuid) 72 | state.replace_workarea(workarea) 73 | } else { 74 | state.replace_grid(grid) 75 | } 76 | return true 77 | } 78 | 79 | fn add_window_in_active_workarea(window core.Window, state &core.State, monitor &core.Monitor) bool { 80 | mut state0 := unsafe {&state} 81 | mut active := state0.workareas.len <= state.monitors.len 82 | 83 | for _, mut workarea in state0.workareas { 84 | if workarea.active && workarea.monitor == monitor.id { 85 | mut grid := state.get_grid_by_uuid(workarea.grid_idx) 86 | grid.add_window(window, workarea, state) 87 | return true 88 | } 89 | } 90 | uuid := rand.uuid_v4() 91 | grid0 := core.new_grid_for_window(window, monitor.workarea) 92 | state.add_grid(grid0) 93 | grid := state0.grids.last() 94 | state0.workareas << core.Workarea{ 95 | uuid: uuid 96 | monitor: monitor.id 97 | idx: state.workareas.len 98 | grid_idx: grid.uuid 99 | active: active 100 | } 101 | wk := state.workareas.last() 102 | state0.add_windows_reference(window, grid, wk) 103 | return true 104 | } 105 | 106 | fn toggle_disabled(state &core.State) { 107 | state.toggle_disabled() 108 | if state.disabled { 109 | core.debug('DISABLE A3wm') 110 | } else { 111 | state.update_render_grid() 112 | state.render_grid() 113 | core.debug('ENABLE A3wm') 114 | } 115 | } 116 | 117 | 118 | pub fn fill_color(hwnd C.HWND, state &core.State) { 119 | ps := winapi.PaintStruct{} 120 | brush := C.CreateSolidBrush(state.topbar_bgcolor) 121 | C.GetClientRect(hwnd, &ps); 122 | hdc := C.BeginPaint(hwnd, &ps) 123 | C.FillRect(hdc, &ps.rcPaint, brush) 124 | C.SetBkColor(hdc, state.topbar_bgcolor) 125 | print_text(hdc, 'A3 Window Manager', 10, 2) 126 | monitor := get_monitor(state) or { return } 127 | w := button(state, 'Reset', monitor.width - 250, 5) 128 | button(state, 'Disable', monitor.width - 250 - w, 5) 129 | C.SetTextColor(hdc, state.topbar_txtcolor) 130 | mut left := print_text(hdc, '${state.windows.len} windows', 10, 25) 131 | left = print_text(hdc, '${state.grids.len} grids', left + 10, 25) 132 | print_text(hdc, '${state.monitors.len} monitors', left + 10, 25) 133 | 134 | print_clock(hdc, monitor) 135 | C.EndPaint(hwnd, &ps) 136 | } 137 | 138 | fn button(state &core.State, message string, x int , y int) int { 139 | class :=wchar.from_string('BUTTON') 140 | message0 :=wchar.from_string(message) 141 | w := (message.len*10 + 20) 142 | C.CreateWindow(class, message0, C.WS_TABSTOP | C.WS_VISIBLE | C.WS_CHILD | C.BS_DEFPUSHBUTTON, x-w,y,w,40, state.handler, unsafe {nil}, state.instance, unsafe {nil}) 143 | return w + 10 144 | } 145 | fn get_monitor(state &core.State) ?core.Rect { 146 | for _, monitor in state.monitors { 147 | if monitor.size.left == 0 { 148 | return monitor.size 149 | } 150 | } 151 | return none 152 | } 153 | fn print_clock(hdc C.HDC, monitor core.Rect) { 154 | text := '18:19 03/01/2024' 155 | C.SetTextColor(hdc, 0x00000000) 156 | C.TextOutA(hdc, monitor.width - (text.len*10), 10, text.str, text.len); 157 | } 158 | fn print_text(hdc C.HDC, text string, x int, y int) int { 159 | C.TextOutA(hdc, x, y, text.str, text.len); 160 | return x + (text.len*10) 161 | } -------------------------------------------------------------------------------- /src/core/grid.v: -------------------------------------------------------------------------------- 1 | module core 2 | 3 | import math 4 | import rand 5 | 6 | type GridAddress = string 7 | type WindowAddress = string 8 | struct None {} 9 | 10 | pub type GridWindow = GridAddress | WindowAddress | None 11 | pub type MaybeGrid = GridAddress | None 12 | 13 | pub struct Grid { 14 | pub mut: 15 | uuid string 16 | idx int 17 | direction WindowPosition 18 | elem1 GridWindow = None{} 19 | elem2 GridWindow = None{} 20 | active1 bool 21 | active2 bool 22 | axis u8 = 50 23 | // 0-100 24 | fullscreen bool 25 | parent_grid MaybeGrid = None{} 26 | // left_grid MaybeGrid = None{} 27 | // right_grid MaybeGrid = None{} 28 | // top_grid MaybeGrid = None{} 29 | // bottom_grid MaybeGrid = None{} 30 | rect Rect 31 | } 32 | 33 | pub fn (gd GridWindow) has_element() bool { 34 | return match gd { 35 | GridAddress{ true } 36 | WindowAddress { true } 37 | None { false } 38 | } 39 | } 40 | pub fn (gd GridWindow) get_uuid() !string { 41 | return match gd { 42 | GridAddress{ string(gd) } 43 | WindowAddress { string(gd) } 44 | None { error('not valid GridWindow') } 45 | } 46 | } 47 | pub fn (gd GridWindow) get_window_address() !string { 48 | return match gd { 49 | WindowAddress { string(gd) } 50 | else { error('not valid GridWindow') } 51 | } 52 | } 53 | pub fn (gd GridWindow) get_grid_address() !string { 54 | return match gd { 55 | WindowAddress { string(gd) } 56 | else { error('not valid GridWindow') } 57 | } 58 | } 59 | pub fn (g &Grid) ptr_str() string { 60 | return '${g:p}' 61 | } 62 | fn (g &Grid) set_axis(axis u8) { 63 | mut g0 := unsafe { &g } 64 | g0.axis = axis 65 | } 66 | fn (g &Grid) set_width_by_axis(width int, axis u8) { 67 | mut g0 := unsafe { &g } 68 | new_width := int(width * f32(axis)/100.0) 69 | g0.rect = Rect{...g0.rect, width: new_width} 70 | } 71 | fn (g &Grid) get_deep_rects(state &State) !map[string][]int { 72 | mut rets := map[string][]int{} 73 | if g.active1 { 74 | elem1 := g.elem1 75 | match elem1 { 76 | WindowAddress { 77 | hwnd := g.elem1.get_uuid() or { return error('dont find window') } 78 | rets[hwnd] = g.get_elem_rect_for1().to_list_int() 79 | } 80 | GridAddress { 81 | grid_uuid := g.elem1.get_uuid() or { return error('dont find window') } 82 | grid := state.get_grid_by_uuid(grid_uuid) 83 | rects0 := grid.get_deep_rects(state) or { return error('dont find rects') } 84 | for hwnd, r in rects0 { 85 | rets[hwnd] = r 86 | } 87 | } 88 | None { return error('dont find elemnt') } 89 | } 90 | } 91 | if g.active2 { 92 | elem2 := g.elem2 93 | match elem2 { 94 | WindowAddress { 95 | hwnd := g.elem2.get_uuid() or { return error('dont find window') } 96 | rets[hwnd] = g.get_elem_rect_for2().to_list_int() 97 | } 98 | GridAddress { 99 | grid_uuid := g.elem2.get_uuid() or { return error('dont find window') } 100 | grid := state.get_grid_by_uuid(grid_uuid) 101 | rects0 := grid.get_deep_rects(state) or { return error('dont find rects') } 102 | for hwnd, r in rects0 { 103 | rets[hwnd] = r 104 | } 105 | } 106 | None { return error('dont find elemnt') } 107 | } 108 | } 109 | return rets 110 | } 111 | pub fn new_grid_for_window(w Window, rect Rect) Grid { 112 | uuid := rand.uuid_v4() 113 | return Grid{ 114 | uuid: uuid 115 | elem1: WindowAddress(w.ptr.str()) 116 | active1: true 117 | axis: 100 118 | rect: rect 119 | } 120 | } 121 | pub fn new_grid_from_before(grid Grid, rect Rect) &Grid { 122 | uuid := rand.uuid_v4() 123 | return &Grid{ 124 | uuid: uuid 125 | elem1: grid.elem1 126 | active1: true 127 | elem2: grid.elem2 128 | active2: true 129 | axis: grid.axis 130 | parent_grid: GridAddress(grid.uuid) 131 | rect: rect 132 | } 133 | } 134 | pub fn (g Grid) all_actives() bool { 135 | return g.active1 && g.active2 136 | } 137 | 138 | pub fn (mut g Grid) remove_window(hwnd C.HWND, state &State) (bool, &Grid) { 139 | pos := g.which_elem_position(hwnd.str()) 140 | if pos == 1 { 141 | g.elem1 = None{} 142 | g.active1 = false 143 | if g.elem2 is GridAddress { 144 | uuid := g.elem2.get_uuid() or { return false, unsafe {nil} } 145 | mut gd := state.get_grid_by_uuid(uuid) 146 | gd.restore_parent_size(g) 147 | gd.parent_grid = None{} 148 | return true, gd 149 | } 150 | g.axis = 100 151 | 152 | state.replace_grid(g) 153 | } else if pos == 2 { 154 | g.elem2 = None{} 155 | g.active2 = false 156 | 157 | if g.elem1 is GridAddress { 158 | uuid := g.elem1.get_uuid() or { return false, unsafe {nil} } 159 | mut gd := state.get_grid_by_uuid(uuid) 160 | gd.restore_parent_size(g) 161 | gd.parent_grid = None{} 162 | return true, gd 163 | } 164 | g.axis = 100 165 | state.replace_grid(g) 166 | } else { 167 | return false, unsafe {nil} 168 | } 169 | 170 | return false, unsafe {nil} 171 | } 172 | 173 | pub fn (mut g Grid) replace_window(pos int, hwnd C.HWND) { 174 | 175 | if pos == 1 { 176 | g.elem1 = WindowAddress(hwnd.str()) 177 | } else if pos == 2 { 178 | g.elem2 = WindowAddress(hwnd.str()) 179 | } 180 | } 181 | 182 | pub fn (mut g Grid) add_window(window Window, workarea &Workarea, state &State) { 183 | window_str := window.ptr.str() 184 | window_active := window.active 185 | if g.elem1 is None { 186 | g.elem1 = WindowAddress(window_str) 187 | g.active1 = window_active 188 | state.add_windows_reference(window, g, workarea) 189 | } else if g.elem2 is None { 190 | g.elem2 = WindowAddress(window_str) 191 | g.active2 = window_active 192 | state.add_windows_reference(window, g, workarea) 193 | } else { 194 | rect := g.get_splitted_rect(.horizontal) 195 | grid := new_grid_from_before(g, rect) 196 | state.add_grid(grid) 197 | state.update_windows_grid_reference_from_grid(grid) 198 | state.add_windows_reference(window, g, workarea) 199 | g.elem1 = GridAddress(grid.uuid) 200 | g.active1 = true 201 | g.elem2 = WindowAddress(window_str) 202 | g.active1 = window_active 203 | g.axis = 50 204 | state.replace_grid(g) 205 | state.replace_workarea(workarea) 206 | return 207 | } 208 | if g.all_actives() { 209 | g.axis = 50 210 | } 211 | } 212 | 213 | pub fn (g Grid) is_elem_active(el int) bool { 214 | return match el { 215 | 1 { g.active1 } 216 | 2 { g.active2 } 217 | else { false } 218 | } 219 | } 220 | 221 | pub fn (g &Grid) restore_parent_size(g_parent &Grid) { 222 | mut g0 := unsafe { &g} 223 | match g0.direction { 224 | .horizontal { 225 | g0.rect = Rect{...g0.rect, width: g_parent.rect.width} 226 | } 227 | .vertical { 228 | g0.rect = Rect{...g0.rect, height: g_parent.rect.height} 229 | } 230 | } 231 | } 232 | 233 | pub fn (g Grid) get_total_rect() Rect { 234 | return g.rect 235 | } 236 | pub fn (g Grid) get_splitted_rect(direction WindowPosition) Rect { 237 | return match direction { 238 | .horizontal { 239 | Rect{ 240 | left: g.rect.left 241 | top: g.rect.top 242 | width: g.rect.width/2 243 | height: g.rect.height 244 | } 245 | } 246 | .vertical { 247 | Rect{ 248 | left: g.rect.left 249 | top: g.rect.top 250 | width: g.rect.width 251 | height: g.rect.height/2 252 | } 253 | } 254 | } 255 | 256 | } 257 | pub fn (g Grid) get_elem_rect_for1() Rect { 258 | _ := g.elem1.get_uuid() or {return Rect{valid: false}} 259 | left := g.rect.left 260 | top := g.rect.top 261 | width := g.rect.width 262 | height := g.rect.height 263 | percent := f32(g.axis) / 100.0 264 | width1 := int(width * percent) 265 | height1 := int(height * percent) 266 | match g.direction { 267 | .horizontal { 268 | return Rect{ 269 | left: left 270 | top: top 271 | width: width1 272 | height: height 273 | } 274 | } 275 | .vertical { 276 | return Rect{ 277 | left: left 278 | top: top 279 | width: width 280 | height: height1 281 | } 282 | } 283 | } 284 | } 285 | pub fn (g Grid) get_elem_rect_for2() Rect { 286 | _ := g.elem2.get_uuid() or {return Rect{valid: false}} 287 | fix_axis := 100 288 | cfix_axis := 0 289 | left := g.rect.left 290 | top := g.rect.top 291 | width := g.rect.width 292 | height := g.rect.height 293 | percent := f32(math.abs(g.axis - fix_axis)) / 100.0 294 | cpercent := f32(math.abs(g.axis - cfix_axis)) / 100.0 295 | width1 := int(width * cpercent) 296 | width2 := int(width * percent) 297 | height1 := int(height * cpercent) 298 | height2 := int(height * percent) 299 | match g.direction { 300 | .horizontal { 301 | return Rect{ 302 | left: left + width1 303 | top: top 304 | width: width2 305 | height: height 306 | } 307 | } 308 | .vertical { 309 | return Rect{ 310 | left: left 311 | top: top + height1 312 | width: width 313 | height: height2 314 | } 315 | } 316 | } 317 | } 318 | 319 | pub fn (g Grid) is_same(pos int, str string) bool { 320 | match pos { 321 | 1 { 322 | addrs := g.elem1.get_uuid() or { return false } 323 | return addrs == str 324 | } 325 | 2 { 326 | addrs := g.elem2.get_uuid() or { return false } 327 | return addrs == str 328 | } 329 | else {} 330 | } 331 | return false 332 | } 333 | 334 | pub fn (mut g Grid) toggle_direction() { 335 | match g.direction { 336 | .horizontal { g.direction = .vertical} 337 | .vertical { g.direction = .horizontal} 338 | } 339 | } 340 | 341 | pub fn (g &Grid) which_elem_position(str string) int { 342 | if g.is_same(1, str) { 343 | return 1 344 | } else if g.is_same(2, str) { 345 | return 2 346 | } else { 347 | return 0 348 | } 349 | } 350 | 351 | fn maybe_update_axis_and_rect(parent_grid MaybeGrid, uuid string, pos int, rect C.RECT, new_axis u8, state &State) bool { 352 | match parent_grid { 353 | GridAddress{ 354 | grid := state.get_grid_by_uuid(parent_grid) 355 | if pos == 2 && grid.elem1 is GridAddress { 356 | elem0 := grid.elem1 as GridAddress 357 | if string(elem0) == uuid { 358 | 359 | rect0 := C.RECT{left: grid.rect.left, top: grid.rect.top, right: rect.right, bottom: grid.rect.top + grid.rect.height} 360 | grid.parse_axis_with_pos(rect0, 1, state) 361 | return true 362 | } 363 | } else if pos == 1 && grid.elem2 is GridAddress{ 364 | elem0 := grid.elem2 as GridAddress 365 | if string(elem0) == uuid { 366 | rect0 := C.RECT{left: rect.left, top: grid.rect.top, right: grid.rect.left+grid.rect.width, bottom: grid.rect.top + grid.rect.height} 367 | grid.parse_axis_with_pos(rect0, 2, state) 368 | return true 369 | } 370 | } 371 | } 372 | None{ } 373 | } 374 | return false 375 | 376 | } 377 | 378 | pub fn (g &Grid) get_from_parent(state &State) &Grid { 379 | gid := g.parent_grid 380 | return match gid { 381 | GridAddress{ 382 | grid := state.get_grid_by_uuid(gid) 383 | grid0 := grid.get_from_parent(state) 384 | 385 | if grid0.uuid != '' { 386 | grid0 387 | } else if grid.uuid != '' { 388 | grid 389 | } else { 390 | &Grid{} 391 | } 392 | } 393 | None{ &Grid{} } 394 | } 395 | } 396 | pub fn (g &Grid) maybe_update_grid_width_by_el(el int, total_width int, axis int, state &State) { 397 | mut gid0 := GridWindow(None{}) 398 | if el == 1 { 399 | gid0 = g.elem1 400 | } if el == 2 { 401 | gid0 = g.elem2 402 | } 403 | match gid0 { 404 | GridAddress{ 405 | gid := gid0 as GridAddress 406 | grid := state.get_grid_by_uuid(gid) 407 | grid.set_width_by_axis(total_width,u8(axis)) 408 | } 409 | else{} 410 | } 411 | } 412 | pub fn (g &Grid) parse_axis_with_pos(rect C.RECT, pos int, state &State) Action { 413 | mut axis := 0 414 | match g.direction { 415 | .horizontal { 416 | total_width := g.rect.width 417 | old_top := g.rect.top 418 | new_top := rect.top 419 | if math.abs(old_top - new_top) > 3 { 420 | return .swap_window 421 | } 422 | if pos == 1 { 423 | delta_left := math.abs(g.rect.left - rect.left) 424 | // e o delta left for maior que 2 425 | // entao atualiza o grid se existir 426 | if delta_left > 2 && maybe_update_axis_and_rect(g.parent_grid, g.uuid, 1, rect, u8(axis), state) { 427 | parent := g.get_from_parent(state) 428 | if parent.uuid != '' { 429 | g.set_width_by_axis(parent.rect.width,parent.axis) 430 | } 431 | return .drag_axis 432 | } 433 | else { 434 | right1 := rect.right - g.rect.left 435 | axis = int(f32(right1) / total_width * 100.0) 436 | g.maybe_update_grid_width_by_el(2, total_width, axis, state) 437 | } 438 | 439 | } else if pos == 2 { 440 | right1 := g.rect.left + g.rect.width 441 | right2 := rect.right 442 | delta_right := math.abs(right1 - right2) 443 | // e o delta right for maior que 2 444 | // entao atualiza o grid se existir 445 | if delta_right > 2 && maybe_update_axis_and_rect(g.parent_grid, g.uuid, 2, rect, u8(axis), state) { 446 | parent := g.get_from_parent(state) 447 | if parent.uuid != '' { 448 | g.set_width_by_axis(parent.rect.width,parent.axis) 449 | } 450 | return .drag_axis 451 | } 452 | else { 453 | left1 := rect.left - g.rect.left 454 | axis = int(f32(left1) / total_width * 100.0) 455 | g.maybe_update_grid_width_by_el(1, total_width, axis, state) 456 | } 457 | 458 | } 459 | 460 | } 461 | .vertical { 462 | total_height := g.rect.height 463 | old_left := g.rect.left 464 | new_left := rect.left 465 | if math.abs(old_left - new_left) > 3 { 466 | return .swap_window 467 | } 468 | if pos == 1 { 469 | bottom1 := rect.bottom - g.rect.top 470 | axis = int(f32(bottom1) / total_height * 100.0) 471 | } else if pos == 2 { 472 | top1 := rect.top - g.rect.top 473 | axis = int(f32(top1) / total_height * 100.0) 474 | } 475 | } 476 | } 477 | if u8(axis) > 100 { return .nothing } 478 | g.set_axis(u8(axis)) 479 | return .drag_axis 480 | } 481 | pub fn (g &Grid) parse_axis(rect C.RECT, hwnd C.HWND, state &State) Action { 482 | el := g.which_elem_position(hwnd.str()) 483 | if el <= 2 { 484 | return g.parse_axis_with_pos(rect, el, state) 485 | } else { 486 | debug('not found window') 487 | } 488 | return .nothing 489 | } 490 | --------------------------------------------------------------------------------