diff --git a/.gitignore b/.gitignore index a850548..9624a77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +.vscode +.vs o2048 +o2048.exe +o2048.pdb +o2048.res ols.json o2048.log diff --git a/assets/icon-128x128.ico b/assets/icon-128x128.ico new file mode 100644 index 0000000..a20b01c Binary files /dev/null and b/assets/icon-128x128.ico differ diff --git a/freetype/libs/freetype.lib b/freetype/libs/freetype.lib new file mode 100644 index 0000000..7e029a0 Binary files /dev/null and b/freetype/libs/freetype.lib differ diff --git a/freetype/types.odin b/freetype/types.odin index 003f106..43c89c3 100644 --- a/freetype/types.odin +++ b/freetype/types.odin @@ -16,8 +16,7 @@ FT_Int :: c.int FT_UInt :: c.uint @private FT_Long :: c.long -@private -FT_ULong :: c.ulong +ULong :: c.ulong @private FT_String :: c.char @private @@ -87,6 +86,8 @@ LoadFlags :: enum { FT_LOAD_TARGET_LCD_V = (int(FT_Render_Mode_.LCD_V) & 15) << 16, } +#assert(size_of(OutlineFlags) == 8) + @private OutlineFlags :: enum { NONE = 0x0, @@ -144,7 +145,7 @@ FT_CharMapRec_ :: struct { @private FT_CharMap :: ^FT_CharMapRec_ -#assert(size_of(FT_Bitmap_Size) == 32) +#assert(size_of(FT_Bitmap_Size) == (16 when (size_of(FT_Pos) == 4) else 32)) @private FT_Bitmap_Size :: struct { @@ -180,7 +181,7 @@ FT_Vector :: struct { x, y: FT_Pos, } -#assert(size_of(FT_Glyph_Metrics) == 64) +#assert(size_of(FT_Glyph_Metrics) == (32 when (size_of(FT_Pos) == 4) else 64)) @private FT_Glyph_Metrics :: struct { @@ -210,6 +211,8 @@ FT_Outline :: struct { flags: OutlineFlags, } +#assert(size_of(FT_Glyph_Format) == 8) + @private FT_Glyph_Format :: enum { NONE = (0 << 24) | (0 << 16) | (0 << 8) | 0, @@ -220,9 +223,11 @@ FT_Glyph_Format :: enum { PLOTTER = ('p' << 24) | ('l' << 16) | ('o' << 8) | 't' } -#assert(size_of(FT_GlyphSlotRec_) == 304) +// linux: 304 +// windows: 248 +// windows: 236 packed +#assert(size_of(FT_GlyphSlotRec_) == (248 when ODIN_OS == .Windows else 304)) -@private FT_GlyphSlotRec_ :: struct { library: Library, face: Face, @@ -247,7 +252,7 @@ FT_GlyphSlotRec_ :: struct { subglyphs: FT_SubGlyph, control_data: rawptr, - control_len: i64, + control_len: c.long, lsb_delta: FT_Pos, rsb_delta: FT_Pos, @@ -260,7 +265,7 @@ FT_GlyphSlotRec_ :: struct { @private FT_GlyphSlot :: ^FT_GlyphSlotRec_ -#assert(size_of(FT_Size_Metrics) == 56) +#assert(size_of(FT_Size_Metrics) == (28 when size_of(FT_Pos) == 4 else 56)) @private FT_Size_Metrics :: struct { @@ -276,7 +281,10 @@ FT_Size_Metrics :: struct { max_advance: FT_Pos, /* max horizontal advance, in 26.6 pixels */ } -#assert(size_of(FT_SizeRec) == 88) +// linux: 88 +// windows: 64 (has 4 bytes padding) +// windows: 60 packed +#assert(size_of(FT_SizeRec) == (64 when ODIN_OS == .Windows else 88)) @private FT_SizeRec :: struct { @@ -343,7 +351,10 @@ FT_ListRec :: struct { tail: FT_ListNode, } -#assert(size_of(FT_FaceRec_) == 248) +// linux: 248 +// windows: 216 +// windows packed: 204 +#assert(size_of(FT_FaceRec_) == 216) @private FT_FaceRec_ :: struct { diff --git a/freetype/wrapper.odin b/freetype/wrapper.odin index d92f0a1..afd9363 100644 --- a/freetype/wrapper.odin +++ b/freetype/wrapper.odin @@ -1,6 +1,9 @@ +// based on system packaged freetype 2.11.1 on Ubuntu 22.04 +// @INCOMPLETE package freetype -foreign import freetype "system:freetype" +when ODIN_OS == .Linux { foreign import freetype "system:freetype" } +when ODIN_OS == .Windows { foreign import freetype "libs/freetype.lib" } foreign freetype { @(link_name = "FT_Init_FreeType") @@ -10,7 +13,7 @@ foreign freetype { @(link_name = "FT_Set_Pixel_Sizes") Set_Pixel_Sizes :: proc(face: Face, pixel_width: FT_UInt, pixel_height: FT_UInt) -> FT_Error --- @(link_name = "FT_Load_Char") - Load_Char :: proc(face: Face, char_code: FT_ULong, load_flags: LoadFlags) -> FT_Error --- + Load_Char :: proc(face: Face, char_code: ULong, load_flags: LoadFlags) -> FT_Error --- @(link_name = "FT_Done_Face") Done_Face :: proc(face: Face) -> FT_Error --- @(link_name = "FT_Done_FreeType") diff --git a/glx/constants.odin b/glx/constants.odin index f79a313..847bec0 100644 --- a/glx/constants.odin +++ b/glx/constants.odin @@ -1,19 +1,20 @@ +// +build linux package glx import x11 "vendor:x11/xlib" Drawable :: x11.XID -RENDER_TYPE :: 0x8011 -RGBA_BIT :: 0x00000001 -RED_SIZE :: 8 -GREEN_SIZE :: 9 -BLUE_SIZE :: 10 -ALPHA_SIZE :: 11 -DEPTH_SIZE :: 12 -DOUBLEBUFFER :: 5 -SAMPLES :: 100001 +RENDER_TYPE :: 0x8011 +RGBA_BIT :: 0x00000001 +RED_SIZE :: 8 +GREEN_SIZE :: 9 +BLUE_SIZE :: 10 +ALPHA_SIZE :: 11 +DEPTH_SIZE :: 12 +DOUBLEBUFFER :: 5 +SAMPLES :: 100001 CONTEXT_MAJOR_VERSION_ARB :: 0x2091 CONTEXT_MINOR_VERSION_ARB :: 0x2092 CONTEXT_PROFILE_MASK_ARB :: 0x9126 -CONTEXT_CORE_PROFILE_BIT_ARB :: 0x00000001 +CONTEXT_CORE_PROFILE_BIT_ARB :: 0x00000001 diff --git a/glx/wrappers.odin b/glx/wrappers.odin index 3e8761f..72a6a7a 100644 --- a/glx/wrappers.odin +++ b/glx/wrappers.odin @@ -1,3 +1,4 @@ +// +build linux package glx import x11 "vendor:x11/xlib" diff --git a/main.odin b/main.odin index 4b3e12c..fdcca8d 100644 --- a/main.odin +++ b/main.odin @@ -1,11 +1,10 @@ package main -import gl "vendor:OpenGL" -import "glx" import "zephr" font_path :: "assets/fonts/Rubik/Rubik-VariableFont_wght.ttf" -icon_path :: "assets/icon-128x128.png" +when ODIN_OS == .Windows {icon_path :: "assets/icon-128x128.ico"} +when ODIN_OS == .Linux {icon_path :: "assets/icon-128x128.png"} title :: "2048" main :: proc() { @@ -15,4 +14,3 @@ main :: proc() { zephr.deinit() } - diff --git a/o2048.rc b/o2048.rc new file mode 100644 index 0000000..a527270 --- /dev/null +++ b/o2048.rc @@ -0,0 +1 @@ +icon ICON "assets/icon-128x128.ico" \ No newline at end of file diff --git a/xcursor/wrapper.odin b/xcursor/wrapper.odin index 82daf85..5d55358 100644 --- a/xcursor/wrapper.odin +++ b/xcursor/wrapper.odin @@ -1,3 +1,4 @@ +// +build linux package xcursor import x11 "vendor:x11/xlib" diff --git a/zephr/os_linux.odin b/zephr/os_linux.odin new file mode 100644 index 0000000..0f8d0b9 --- /dev/null +++ b/zephr/os_linux.odin @@ -0,0 +1,529 @@ +// +build linux +// +private +package zephr + +import x11 "vendor:x11/xlib" + +import "../glx" +import "../xcursor" + +@(private="file") +PropModeReplace :: 0 +@(private="file") +XA_ATOM :: x11.Atom(4) +@(private="file") +XA_STRING :: x11.Atom(31) +@(private="file") +XA_CARDINAL :: x11.Atom(6) + +@(private="file") +x11_display : ^x11.Display +@(private="file") +x11_window : x11.Window +@(private="file") +x11_colormap : x11.Colormap +@(private="file") +glx_context : glx.Context + +@(private="file") +x11_go_fullscreen :: proc() { + // remove the resizing constraint before going fullscreen so WMs such as gnome + // can add the _NET_WM_ACTION_FULLSCREEN action to the _NET_WM_ALLOWED_ACTIONS atom + // and properly fullscreen the window + size_hints := x11.XAllocSizeHints() + + zephr_ctx.window.pre_fullscreen_size.x = zephr_ctx.window.size.x + zephr_ctx.window.pre_fullscreen_size.y = zephr_ctx.window.size.y + if (size_hints != nil) { + size_hints.flags = {.PPosition, .PSize} + size_hints.width = cast(i32)zephr_ctx.window.size.x + size_hints.height = cast(i32)zephr_ctx.window.size.y + x11.XSetWMNormalHints(x11_display, x11_window, size_hints) + x11.XFree(size_hints) + } + + xev: x11.XEvent + wm_state := x11.XInternAtom(x11_display, "_NET_WM_STATE", false) + fullscreen := x11.XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", false) + xev.type = .ClientMessage + xev.xclient.window = x11_window + xev.xclient.message_type = wm_state + xev.xclient.format = 32 + xev.xclient.data.l[0] = 1 // _NET_WM_STATE_ADD + xev.xclient.data.l[1] = cast(int)fullscreen + xev.xclient.data.l[2] = 0 + x11.XSendEvent(x11_display, x11.XDefaultRootWindow(x11_display), false, + {.SubstructureNotify, .SubstructureRedirect}, &xev) +} + +@(private="file") +x11_return_fullscreen :: proc() { + xev: x11.XEvent + wm_state := x11.XInternAtom(x11_display, "_NET_WM_STATE", false) + fullscreen := x11.XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", false) + xev.type = .ClientMessage + xev.xclient.window = x11_window + xev.xclient.message_type = wm_state + xev.xclient.format = 32 + xev.xclient.data.l[0] = 0 // _NET_WM_STATE_REMOVE + xev.xclient.data.l[1] = cast(int)fullscreen + xev.xclient.data.l[2] = 0 + x11.XSendEvent(x11_display, x11.XDefaultRootWindow(x11_display), false, + {.SubstructureNotify, .SubstructureRedirect}, &xev) + + // restore the resizing constraint as well as the pre-fullscreen window size + // when returning from fullscreen + size_hints := x11.XAllocSizeHints() + + if (size_hints != nil) { + size_hints.flags = {.PPosition, .PSize} + size_hints.width = cast(i32)zephr_ctx.window.pre_fullscreen_size.x + size_hints.height = cast(i32)zephr_ctx.window.pre_fullscreen_size.y + if (zephr_ctx.window.non_resizable) { + size_hints.flags |= {.PMinSize, .PMaxSize} + size_hints.min_width = cast(i32)zephr_ctx.window.pre_fullscreen_size.x + size_hints.min_height = cast(i32)zephr_ctx.window.pre_fullscreen_size.y + size_hints.max_width = cast(i32)zephr_ctx.window.pre_fullscreen_size.x + size_hints.max_height = cast(i32)zephr_ctx.window.pre_fullscreen_size.y + } + x11.XSetWMNormalHints(x11_display, x11_window, size_hints) + x11.XFree(size_hints) + } +} + +backend_toggle_fullscreen :: proc(fullscreen: bool) { + if fullscreen { + x11_return_fullscreen() + } else { + x11_go_fullscreen() + } +} + +@(private="file") +x11_assign_window_icon :: proc(icon_path: cstring, window_title: cstring) { + icon_width, icon_height: i32 + icon_data := image.load(icon_path, &icon_width, &icon_height, nil, 4) + defer image.image_free(icon_data) + assert(icon_data != nil, "Failed to load icon image") + + target_size := 2 + icon_width * icon_height + + data := make([]u64, target_size) + + // first two elements are width and height + data[0] = cast(u64)icon_width + data[1] = cast(u64)icon_height + + for i in 0..<(icon_width * icon_height) { + data[i + 2] = (cast(u64)icon_data[i * 4] << 16) | (cast(u64)icon_data[i * 4 + 1] << 8) | (cast(u64)icon_data[i * 4 + 2] << 0) | (cast(u64)icon_data[i * 4 + 3] << 24) + } + + net_wm_icon := x11.XInternAtom(x11_display, "_NET_WM_ICON", false) + x11.XChangeProperty(x11_display, x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, raw_data(data), target_size) +} + +backend_get_screen_size :: proc() -> Vec2 { + screen := x11.XDefaultScreenOfDisplay(x11_display) + + return Vec2{cast(f32)screen.width, cast(f32)screen.height} +} + +@(private="file") +x11_resize_window :: proc() { + win_attrs : x11.XWindowAttributes + x11.XGetWindowAttributes(x11_display, x11_window, &win_attrs) + gl.Viewport(0, 0, win_attrs.width, win_attrs.height) +} + +@(private="file") +x11_create_window :: proc(window_title: cstring, window_size: Vec2, icon_path: cstring, window_non_resizable: bool) { + context.logger = logger + x11_display = x11.XOpenDisplay(nil) + + if x11_display == nil { + log.error("Failed to open X11 display") + return + } + + screen_num := x11.XDefaultScreen(x11_display) + root := x11.XRootWindow(x11_display, screen_num) + visual := x11.XDefaultVisual(x11_display, screen_num) + + x11_colormap = x11.XCreateColormap(x11_display, root, visual, x11.ColormapAlloc.AllocNone) + + attributes: x11.XSetWindowAttributes + attributes.event_mask = {.Exposure, .KeyPress, .KeyRelease, + .StructureNotify, .ButtonPress, .ButtonRelease, .PointerMotion} + attributes.colormap = x11_colormap + + screen := x11.XDefaultScreenOfDisplay(x11_display) + + window_start_x := screen.width / 2 - cast(i32)window_size.x / 2 + window_start_y := screen.height / 2 - cast(i32)window_size.y / 2 + + x11_window = x11.XCreateWindow(x11_display, root, window_start_x, window_start_y, cast(u32)window_size.x, cast(u32)window_size.y, 0, + x11.XDefaultDepth(x11_display), .InputOutput, visual, + {.CWColormap, .CWEventMask}, &attributes) + + if (icon_path != "") { + x11_assign_window_icon(icon_path, window_title) + } + + // Hints to the WM that the window is a normal window + // Of course this is only a hint and the WM can ignore it + net_wm_window_type := x11.XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", false) + net_wm_window_type_normal := x11.XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", false) + x11.XChangeProperty(x11_display, x11_window, net_wm_window_type, XA_ATOM, 32, PropModeReplace, &net_wm_window_type_normal, 1) + + wm_delete_window := x11.XInternAtom(x11_display, "WM_DELETE_WINDOW", false) + x11.XSetWMProtocols(x11_display, x11_window, &wm_delete_window, 1) + zephr_ctx.window_delete_atom = wm_delete_window + + // set window name + { + UTF8_STRING := x11.XInternAtom(x11_display, "UTF8_STRING", false) + x11.XStoreName(x11_display, x11_window, window_title) + text_property: x11.XTextProperty + text_property.value = raw_data(string(window_title)) + text_property.format = 8 + text_property.encoding = UTF8_STRING + text_property.nitems = len(window_title) + x11.XSetWMName(x11_display, x11_window, &text_property) + net_wm_name := x11.XInternAtom(x11_display, "_NET_WM_NAME", false) + wm_class := x11.XInternAtom(x11_display, "WM_CLASS", false) + x11.XChangeProperty(x11_display, x11_window, net_wm_name, UTF8_STRING, 8, PropModeReplace, raw_data(string(window_title)), cast(i32)len(window_title)) + x11.XChangeProperty(x11_display, x11_window, wm_class, XA_STRING, 8, PropModeReplace, raw_data(string(window_title)), cast(i32)len(window_title)) + + // name to be displayed when the window is reduced to an icon + net_wm_icon_name := x11.XInternAtom(x11_display, "_NET_WM_ICON_NAME", false) + x11.XChangeProperty(x11_display, x11_window, net_wm_icon_name, UTF8_STRING, 8, PropModeReplace, raw_data(string(window_title)), cast(i32)len(window_title)) + + text_property.encoding = XA_STRING + x11.XSetWMIconName(x11_display, x11_window, &text_property) + + class_hint := x11.XAllocClassHint() + + if (class_hint != nil) { + class_hint.res_name = window_title + class_hint.res_class = window_title + x11.XSetClassHint(x11_display, x11_window, class_hint) + x11.XFree(class_hint) + } + } + + size_hints := x11.XAllocSizeHints() + + if (size_hints != nil) { + size_hints.flags = {.PPosition, .PSize} + /* size_hints->win_gravity = StaticGravity; */ + size_hints.x = window_start_x + size_hints.y = window_start_y + size_hints.width = cast(i32)window_size.x + size_hints.height = cast(i32)window_size.y + if (window_non_resizable) { + size_hints.flags |= {.PMinSize, .PMaxSize} + size_hints.min_width = cast(i32)window_size.x + size_hints.min_height = cast(i32)window_size.y + size_hints.max_width = cast(i32)window_size.x + size_hints.max_height = cast(i32)window_size.y + } + x11.XSetWMNormalHints(x11_display, x11_window, size_hints) + x11.XFree(size_hints) + } + + x11.XMapWindow(x11_display, x11_window) + + gl.load_up_to(3, 3, proc(p: rawptr, name: cstring) { (cast(^rawptr)p)^ = glx.GetProcAddressARB(raw_data(string(name))) }) + + glx_major : i32 + glx_minor : i32 + glx.QueryVersion(x11_display, &glx_major, &glx_minor) + + log.infof("Loaded GLX: %d.%d", glx_major, glx_minor) + + visual_attributes := []i32 { + glx.RENDER_TYPE, glx.RGBA_BIT, + glx.RED_SIZE, 8, + glx.GREEN_SIZE, 8, + glx.BLUE_SIZE, 8, + glx.ALPHA_SIZE, 8, + glx.DEPTH_SIZE, 24, + glx.DOUBLEBUFFER, 1, + glx.SAMPLES, 4, // MSAA + x11.None, + } + + num_fbc: i32 + fbc := glx.ChooseFBConfig(x11_display, screen_num, raw_data(visual_attributes), &num_fbc) + + context_attributes := []i32 { + glx.CONTEXT_MAJOR_VERSION_ARB, 3, + glx.CONTEXT_MINOR_VERSION_ARB, 3, + glx.CONTEXT_PROFILE_MASK_ARB, glx.CONTEXT_CORE_PROFILE_BIT_ARB, + x11.None, + } + + glx_context = glx.CreateContextAttribsARB(x11_display, fbc[0], nil, true, raw_data(context_attributes)) + + if glx_context == nil { + log.error("Failed to create GLX context") + return + } + + glx.MakeCurrent(x11_display, x11_window, glx_context) + + gl_version := gl.GetString(gl.VERSION) + + log.infof("GL Version: %s", gl_version) + + glx.SwapIntervalEXT(x11_display, x11_window, 1) + // we enable blending for text + gl.Enable(gl.BLEND) + gl.Enable(gl.MULTISAMPLE) + gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) + x11_resize_window() + + x11.XFree(fbc) +} + +backend_init :: proc(window_title: cstring, window_size: Vec2, icon_path: cstring, window_non_resizable: bool) { + x11_create_window(window_title, window_size, icon_path, window_non_resizable) +} + +backend_shutdown :: proc() { + glx.MakeCurrent(x11_display, 0, nil) + glx.DestroyContext(x11_display, glx_context) + + x11.XDestroyWindow(x11_display, x11_window) + x11.XFreeColormap(x11_display, x11_colormap) + x11.XCloseDisplay(x11_display) +} + +@(private="file") +x11_mods_to_zephr_mods :: proc(scancode: Scancode, is_press: bool) -> KeyMod { + mods := zephr_ctx.keyboard.mods + + if (is_press) { + if (scancode == .LEFT_SHIFT) { + mods |= {.LEFT_SHIFT, .SHIFT} + } + if (scancode == .RIGHT_SHIFT) { + mods |= {.RIGHT_SHIFT, .SHIFT} + } + if (scancode == .LEFT_CTRL) { + mods |= {.LEFT_CTRL, .CTRL} + } + if (scancode == .RIGHT_CTRL) { + mods |= {.RIGHT_CTRL, .CTRL} + } + if (scancode == .LEFT_ALT) { + mods |= {.LEFT_ALT, .ALT} + } + if (scancode == .RIGHT_ALT) { + mods |= {.RIGHT_ALT, .ALT} + } + if (scancode == .LEFT_META) { + mods |= {.LEFT_META, .META} + } + if (scancode == .RIGHT_META) { + mods |= {.RIGHT_META, .META} + } + if (scancode == .CAPS_LOCK) { + mods |= {.CAPS_LOCK} + } + if (scancode == .NUM_LOCK_OR_CLEAR) { + mods |= {.NUM_LOCK} + } + } else { + if (scancode == .LEFT_SHIFT) { + mods &= ~{.LEFT_SHIFT} + } + if (scancode == .RIGHT_SHIFT) { + mods &= ~{.RIGHT_SHIFT} + } + if (!(.RIGHT_SHIFT in mods) && !(.LEFT_SHIFT in mods)) { + mods &= ~{.SHIFT} + } + + if (scancode == .LEFT_CTRL) { + mods &= ~{.LEFT_CTRL} + } + if (scancode == .RIGHT_CTRL) { + mods &= ~{.RIGHT_CTRL} + } + if (!(.RIGHT_CTRL in mods) && !(.LEFT_CTRL in mods)) { + mods &= ~{.CTRL} + } + + if (scancode == .LEFT_ALT) { + mods &= ~{.LEFT_ALT} + } + if (scancode == .RIGHT_ALT) { + mods &= ~{.RIGHT_ALT} + } + if (!(.RIGHT_ALT in mods) && !(.LEFT_ALT in mods)) { + mods &= ~{.ALT} + } + + if (scancode == .LEFT_META) { + mods &= ~{.LEFT_META} + } + if (scancode == .RIGHT_META) { + mods &= ~{.RIGHT_META} + } + if (!(.RIGHT_META in mods) && !(.LEFT_META in mods)) { + mods &= ~{.META} + } + + if (scancode == .CAPS_LOCK) { + mods &= ~{.CAPS_LOCK} + } + if (scancode == .NUM_LOCK_OR_CLEAR) { + mods &= ~{.NUM_LOCK} + } + } + + zephr_ctx.keyboard.mods = mods + return mods +} + +backend_swapbuffers :: proc() { + glx.SwapBuffers(x11_display, x11_window) +} + +backend_get_os_events :: proc(e_out: ^Event) -> bool { + context.logger = logger + xev: x11.XEvent + + for (cast(bool)x11.XPending(x11_display)) { + x11.XNextEvent(x11_display, &xev) + + if xev.type == .ConfigureNotify { + xce := xev.xconfigure + + if (xce.width != cast(i32)zephr_ctx.window.size.x || xce.height != cast(i32)zephr_ctx.window.size.y) { + zephr_ctx.window.size = Vec2{cast(f32)xce.width, cast(f32)xce.height} + zephr_ctx.projection = orthographic_projection_2d(0, zephr_ctx.window.size.x, zephr_ctx.window.size.y, 0) + x11_resize_window() + + e_out.type = .WINDOW_RESIZED + e_out.window.width = cast(u32)xce.width + e_out.window.height = cast(u32)xce.height + + return true + } + } else if xev.type == .DestroyNotify { + // window destroy event + e_out.type = .WINDOW_CLOSED + + return true + } else if xev.type == .ClientMessage { + // window close event + if (cast(x11.Atom)xev.xclient.data.l[0] == zephr_ctx.window_delete_atom) { + e_out.type = .WINDOW_CLOSED + + return true + } + } else if xev.type == .KeyPress { + xke := xev.xkey + + evdev_keycode := xke.keycode - 8 + scancode := evdev_scancode_to_zephr_scancode_map[evdev_keycode] + + e_out.type = .KEY_PRESSED + e_out.key.scancode = scancode + //e_out.key.code = keycode; + e_out.key.mods = x11_mods_to_zephr_mods(scancode, true) + + return true + } else if xev.type == .KeyRelease { + xke := xev.xkey + + evdev_keycode := xke.keycode - 8 + scancode := evdev_scancode_to_zephr_scancode_map[evdev_keycode] + + e_out.type = .KEY_RELEASED + e_out.key.scancode = scancode + //e_out.key.code = keycode; + e_out.key.mods = x11_mods_to_zephr_mods(scancode, false) + + return true + } else if xev.type == .ButtonPress { + e_out.type = .MOUSE_BUTTON_PRESSED + e_out.mouse.pos = Vec2{cast(f32)xev.xbutton.x, cast(f32)xev.xbutton.y} + zephr_ctx.mouse.pressed = true + + switch (xev.xbutton.button) { + case .Button1: + e_out.mouse.button = .BUTTON_LEFT + zephr_ctx.mouse.button = .BUTTON_LEFT + case .Button2: + e_out.mouse.button = .BUTTON_MIDDLE + zephr_ctx.mouse.button = .BUTTON_MIDDLE + case .Button3: + e_out.mouse.button = .BUTTON_RIGHT + zephr_ctx.mouse.button = .BUTTON_RIGHT + case .Button4: + e_out.type = .MOUSE_SCROLL + e_out.mouse.scroll_direction = .UP + case .Button5: + e_out.type = .MOUSE_SCROLL + e_out.mouse.scroll_direction = .DOWN + case cast(x11.MouseButton)8: // Back + e_out.mouse.button = .BUTTON_BACK + zephr_ctx.mouse.button = .BUTTON_BACK + case cast(x11.MouseButton)9: // Forward + e_out.mouse.button = .BUTTON_FORWARD + zephr_ctx.mouse.button = .BUTTON_FORWARD + case: + log.warnf("Unknown mouse button pressed: %d", xev.xbutton.button) + } + + return true + } else if xev.type == .ButtonRelease { + e_out.type = .MOUSE_BUTTON_RELEASED + e_out.mouse.pos = Vec2{cast(f32)xev.xbutton.x, cast(f32)xev.xbutton.y} + zephr_ctx.mouse.released = true + zephr_ctx.mouse.pressed = false + + switch (xev.xbutton.button) { + case .Button1: + e_out.mouse.button = .BUTTON_LEFT + case .Button2: + e_out.mouse.button = .BUTTON_MIDDLE + case .Button3: + e_out.mouse.button = .BUTTON_RIGHT + case .Button4: + e_out.type = .MOUSE_SCROLL + e_out.mouse.scroll_direction = .UP + case .Button5: + e_out.type = .MOUSE_SCROLL + e_out.mouse.scroll_direction = .DOWN + case cast(x11.MouseButton)8: // Back + e_out.mouse.button = .BUTTON_BACK + zephr_ctx.mouse.button = .BUTTON_BACK + case cast(x11.MouseButton)9: // Forward + e_out.mouse.button = .BUTTON_FORWARD + zephr_ctx.mouse.button = .BUTTON_FORWARD + } + + return true + } else if xev.type == .MappingNotify { + // input device mapping changed + if (xev.xmapping.request != .MappingKeyboard) { + break + } + x11.XRefreshKeyboardMapping(&xev.xmapping) + /* x11_keyboard_map_update(); */ + break + } else if xev.type == .MotionNotify { + e_out.type = .MOUSE_MOVED + e_out.mouse.pos = Vec2{cast(f32)xev.xmotion.x, cast(f32)xev.xmotion.y} + zephr_ctx.mouse.pos = e_out.mouse.pos + + return true + } + } + + return false +} \ No newline at end of file diff --git a/zephr/os_windows.odin b/zephr/os_windows.odin new file mode 100644 index 0000000..f1292ac --- /dev/null +++ b/zephr/os_windows.odin @@ -0,0 +1,280 @@ +// +build windows +// +private +package zephr + +import "core:log" +import "core:strings" +import "core:strconv" +import "core:os" +import win32 "core:sys/windows" + +import gl "vendor:OpenGL" + +@(private="file") +hwnd : win32.HWND +@(private="file") +device_ctx : win32.HDC +@(private="file") +wgl_context : win32.HGLRC + +@(private="file") +temp_hwnd : win32.HWND +@(private="file") +temp_device_ctx : win32.HDC +@(private="file") +temp_wgl_context: win32.HGLRC + +backend_get_screen_size :: proc() -> Vec2 { + screen_size := Vec2{ + cast(f32)win32.GetSystemMetrics(win32.SM_CXSCREEN), + cast(f32)win32.GetSystemMetrics(win32.SM_CYSCREEN), + } + return screen_size +} + +backend_toggle_fullscreen :: proc(fullscreen: bool) { + // TODO: +} + +@(private="file") +init_legacy_gl :: proc(class_name: win32.wstring, hInstance: win32.HINSTANCE) { + temp_hwnd = win32.CreateWindowExW( + 0, + class_name, + win32.L("Fake Window"), + win32.WS_OVERLAPPEDWINDOW, + + 0, 0, 1, 1, + + nil, + nil, + hInstance, + nil, + ) + + if temp_hwnd == nil { + log.fatal("Failed to create fake window") + } + + temp_device_ctx = win32.GetDC(temp_hwnd) + + if temp_device_ctx == nil { + log.error("Failed to create device context for fake window") + } + + pfd := win32.PIXELFORMATDESCRIPTOR{ + nSize = size_of(win32.PIXELFORMATDESCRIPTOR), + nVersion = 1, + dwFlags = win32.PFD_DRAW_TO_WINDOW | win32.PFD_SUPPORT_OPENGL, + iPixelType = win32.PFD_TYPE_RGBA, + cColorBits = 32, + cAlphaBits = 8, + cDepthBits = 24, + iLayerType = win32.PFD_MAIN_PLANE, + } + pixel_format := win32.ChoosePixelFormat(temp_device_ctx, &pfd) + if pixel_format == 0 { + log.error("Failed to choose pixel format for fake window") + } + + status := win32.SetPixelFormat(temp_device_ctx, pixel_format, &pfd) + if !status { + log.error("Failed to set pixel format for fake window") + } + + temp_wgl_context = win32.wglCreateContext(temp_device_ctx) + + if temp_wgl_context == nil { + log.fatal("Failed to create WGL context") + return + } + + gl.load_up_to(3, 3, win32.gl_set_proc_address) + + win32.wglMakeCurrent(temp_device_ctx, temp_wgl_context) + + gl_version := gl.GetString(gl.VERSION) + + strs := strings.split(string(gl_version), " ") + ver := strings.split(strs[0], ".") + gl_major, gl_minor := strconv.atoi(ver[0]), strconv.atoi(ver[1]) + + log.debugf("Fake Window GL: %d.%d", gl_major, gl_minor) + + if !(gl_major > 3 || (gl_major == 3 && gl_minor >= 3)) { + log.fatalf("You need at least OpenGL 3.3 to run this application. Your OpenGL version is %d.%d", gl_major, gl_minor) + os.exit(1) + } +} + +@(private="file") +init_gl :: proc(class_name: win32.wstring, window_title: win32.wstring, window_size: Vec2, hInstance: win32.HINSTANCE) { + screen_size := Vec2{ + cast(f32)win32.GetSystemMetrics(win32.SM_CXSCREEN), + cast(f32)win32.GetSystemMetrics(win32.SM_CYSCREEN), + } + + + win_x := screen_size.x / 2 - window_size.x / 2 + win_y := screen_size.y / 2 - window_size.y / 2 + + hwnd := win32.CreateWindowExW( + 0, + class_name, + window_title, + win32.WS_OVERLAPPEDWINDOW, + + cast(i32)win_x, cast(i32)win_y, cast(i32)window_size.x, cast(i32)window_size.y, + + nil, + nil, + hInstance, + nil, + ) + + if hwnd == nil { + log.fatal("Failed to create window") + return + } + + device_ctx = win32.GetDC(hwnd) + + if device_ctx == nil { + log.fatal("Failed to create device context") + return + } + + wglChoosePixelFormatARB := cast(win32.ChoosePixelFormatARBType)win32.wglGetProcAddress("wglChoosePixelFormatARB") + wglCreateContextAttribsARB := cast(win32.CreateContextAttribsARBType)win32.wglGetProcAddress("wglCreateContextAttribsARB") + wglSwapIntervalEXT := cast(win32.SwapIntervalEXTType)win32.wglGetProcAddress("wglSwapIntervalEXT") + + pixel_attribs := []i32{ + win32.WGL_DRAW_TO_WINDOW_ARB, 1, + win32.WGL_SUPPORT_OPENGL_ARB, 1, + win32.WGL_DOUBLE_BUFFER_ARB, 1, + // TODO: test to see if EXCHANGE actually causes problems in fullscreen + /* WGL_SWAP_EXCHANGE_ARB causes problems with window menu in fullscreen */ + win32.WGL_SWAP_METHOD_ARB, win32.WGL_SWAP_COPY_ARB, + win32.WGL_PIXEL_TYPE_ARB, win32.WGL_TYPE_RGBA_ARB, + win32.WGL_ACCELERATION_ARB, win32.WGL_FULL_ACCELERATION_ARB, + win32.WGL_COLOR_BITS_ARB, 32, + win32.WGL_ALPHA_BITS_ARB, 8, + win32.WGL_DEPTH_BITS_ARB, 24, + win32.WGL_STENCIL_BITS_ARB, 0, + win32.WGL_SAMPLES_ARB, 4, + 0, + } + + ctx_attribs := []i32{ + win32.WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + win32.WGL_CONTEXT_MINOR_VERSION_ARB, 3, + win32.WGL_CONTEXT_PROFILE_MASK_ARB, win32.WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, + } + + pixel_format: i32 + num_formats: u32 + success := wglChoosePixelFormatARB(device_ctx, raw_data(pixel_attribs), nil, 1, &pixel_format, &num_formats) + + if !success { + log.error("Failed to choose pixel format") + return + } + + pfd: win32.PIXELFORMATDESCRIPTOR + win32.DescribePixelFormat(device_ctx, pixel_format, size_of(win32.PIXELFORMATDESCRIPTOR), &pfd) + success = win32.SetPixelFormat(device_ctx, pixel_format, &pfd) + + if !success { + log.error("Failed to set pixel format") + return + } + + wgl_context = wglCreateContextAttribsARB(device_ctx, nil, raw_data(ctx_attribs)) + + if wgl_context == nil { + log.fatal("Failed to create WGL context") + return + } + + win32.wglMakeCurrent(temp_device_ctx, nil) + win32.wglDeleteContext(temp_wgl_context) + win32.ReleaseDC(temp_hwnd, temp_device_ctx) + win32.DestroyWindow(temp_hwnd) + + win32.wglMakeCurrent(device_ctx, wgl_context) + + gl_version := gl.GetString(gl.VERSION) + log.infof("GL Version: %s", gl_version) + + new_success := wglSwapIntervalEXT(1) + if !new_success { + log.error("Failed to turn on v-sync") + } + + gl.load_up_to(3, 3, win32.gl_set_proc_address) + + win32.ShowWindow(hwnd, win32.SW_NORMAL) + + gl.Enable(gl.BLEND) + gl.Enable(gl.MULTISAMPLE) + gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) +} + +backend_init :: proc(window_title: cstring, window_size: Vec2, icon_path: cstring, window_non_resizable: bool) { + context.logger = logger + + class_name := win32.L("zephr.main_window") + window_title := win32.utf8_to_wstring(string(window_title)) + + hInstance := win32.HINSTANCE(win32.GetModuleHandleW(nil)) + //hIcon := win32.LoadImageW(nil, auto_cast win32._IDI_EXCLAMATION, win32.IMAGE_ICON, 0, 0, win32.LR_DEFAULTSIZE | win32.LR_SHARED) // works for system icons + hIcon := win32.LoadImageW(nil, win32.utf8_to_wstring(string(icon_path)), win32.IMAGE_ICON, 0, 0, win32.LR_DEFAULTSIZE | win32.LR_LOADFROMFILE) + wc := win32.WNDCLASSEXW { + // TODO: we can define our own window_proc that can handle events like window resizing and stuff + // we probably want a custom one. + cbSize = size_of(win32.WNDCLASSEXW), + style = win32.CS_HREDRAW | win32.CS_VREDRAW | win32.CS_OWNDC, + lpfnWndProc = win32.DefWindowProcW, + hInstance = hInstance, + lpszClassName = class_name, + hIcon = cast(win32.HICON)hIcon, + hIconSm = cast(win32.HICON)hIcon, // TODO: maybe we can have a 16x16 icon here. can be used on linux too + //hCursor = win32.LoadCursorW(hInstance, win32.utf8_to_wstring(string(win32.IDC_ARROW))), + } + + status := win32.RegisterClassExW(&wc) + + if status == 0 { + log.error("Failed to register class") + } + + // Make process aware of system scaling per monitor + win32.SetThreadDpiAwarenessContext(win32.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + + init_legacy_gl(class_name, hInstance) + init_gl(class_name, window_title, window_size, hInstance) +} + +backend_get_os_events :: proc(e_out: ^Event) -> bool { + msg: win32.MSG + + for win32.PeekMessageW(&msg, hwnd, 0, 0, win32.PM_REMOVE) != win32.FALSE { + win32.TranslateMessage(&msg) + win32.DispatchMessageW(&msg) + } + // TODO: somehow get the events from window proc here??? + + return false +} + +backend_shutdown :: proc() { + win32.wglMakeCurrent(device_ctx, nil) + win32.wglDeleteContext(wgl_context) + win32.ReleaseDC(hwnd, device_ctx) + win32.DestroyWindow(hwnd) +} + +backend_swapbuffers :: proc() { + win32.SwapBuffers(device_ctx) +} \ No newline at end of file diff --git a/zephr/text.odin b/zephr/text.odin index c3f4f00..e35555c 100644 --- a/zephr/text.odin +++ b/zephr/text.odin @@ -80,7 +80,7 @@ init_freetype :: proc(font_path: cstring) -> i32 { tex_width, tex_height: u32 for i in 32..<128 { - if (FT.Load_Char(face, cast(u64)i, .RENDER) != 0) { + if (FT.Load_Char(face, cast(FT.ULong)i, .RENDER) != 0) { log.errorf("Failed to load glyph for char '0x%x'", i) } @@ -95,7 +95,7 @@ init_freetype :: proc(font_path: cstring) -> i32 { for i in 32..<128 { /* while (glyph_idx) { */ - if (FT.Load_Char(face, cast(u64)i, .RENDER) != 0) { + if (FT.Load_Char(face, cast(FT.ULong)i, .RENDER) != 0) { log.errorf("Failed to load glyph for char '0x%x'", i) } @@ -334,6 +334,7 @@ draw_text_batch :: proc(batch: ^GlyphInstanceList) { get_glyph_instance_list_from_text :: proc(text: string, font_size: u32, constraints: UiConstraints, color: Color, alignment: Alignment) -> GlyphInstanceList { constraints := constraints + // TODO: this use_shader might be redundant use_shader(font_shader) text_color := Vec4{ cast(f32)color.r / 255, cast(f32)color.g / 255, cast(f32)color.b / 255, cast(f32)color.a / 255 } @@ -424,4 +425,3 @@ add_text_instance :: proc(batch: ^GlyphInstanceList, text: string, font_size: u3 delete(glyph_instance_list) } - diff --git a/zephr/zephr.odin b/zephr/zephr.odin index fc06e55..88aed40 100644 --- a/zephr/zephr.odin +++ b/zephr/zephr.odin @@ -8,9 +8,6 @@ import x11 "vendor:x11/xlib" import gl "vendor:OpenGL" import "vendor:stb/image" -import "../glx" -import "../xcursor" - Cursor :: enum { ARROW, IBEAM, @@ -349,7 +346,7 @@ Keyboard :: struct { } Context :: struct { - window_delete_atom: x11.Atom, + //window_delete_atom: x11.Atom, should_quit: bool, screen_size: Vec2, window: Window, @@ -357,7 +354,7 @@ Context :: struct { mouse: Mouse, keyboard: Keyboard, cursor: Cursor, - cursors: [Cursor]x11.Cursor, + //cursors: [Cursor]x11.Cursor, ui: Ui, /* ZephrKeyboard keyboard; */ @@ -369,14 +366,6 @@ Context :: struct { projection: Mat4, } -@private -PropModeReplace :: 0 -@private -XA_ATOM :: x11.Atom(4) -@private -XA_STRING :: x11.Atom(31) -@private -XA_CARDINAL :: x11.Atom(6) @private FNV_HASH32_INIT :: 0x811c9dc5 @private @@ -643,306 +632,12 @@ evdev_scancode_to_zephr_scancode_map := []Scancode { 236 = .NULL, // KEY_BATTERY } -@private -x11_display : ^x11.Display -@private -x11_window : x11.Window -@private -x11_colormap : x11.Colormap -@private -glx_context : glx.Context @private zephr_ctx : Context @private logger : log.Logger -//////////////////////////// -// -// X11 -// -/////////////////////////// - - -@private -x11_go_fullscreen :: proc() { - // remove the resizing constraint before going fullscreen so WMs such as gnome - // can add the _NET_WM_ACTION_FULLSCREEN action to the _NET_WM_ALLOWED_ACTIONS atom - // and properly fullscreen the window - size_hints := x11.XAllocSizeHints() - - zephr_ctx.window.pre_fullscreen_size.x = zephr_ctx.window.size.x - zephr_ctx.window.pre_fullscreen_size.y = zephr_ctx.window.size.y - if (size_hints != nil) { - size_hints.flags = {.PPosition, .PSize} - size_hints.width = cast(i32)zephr_ctx.window.size.x - size_hints.height = cast(i32)zephr_ctx.window.size.y - x11.XSetWMNormalHints(x11_display, x11_window, size_hints) - x11.XFree(size_hints) - } - - xev: x11.XEvent - wm_state := x11.XInternAtom(x11_display, "_NET_WM_STATE", false) - fullscreen := x11.XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", false) - xev.type = .ClientMessage - xev.xclient.window = x11_window - xev.xclient.message_type = wm_state - xev.xclient.format = 32 - xev.xclient.data.l[0] = 1 // _NET_WM_STATE_ADD - xev.xclient.data.l[1] = cast(int)fullscreen - xev.xclient.data.l[2] = 0 - x11.XSendEvent(x11_display, x11.XDefaultRootWindow(x11_display), false, - {.SubstructureNotify, .SubstructureRedirect}, &xev) -} - -@private -x11_return_fullscreen :: proc() { - xev: x11.XEvent - wm_state := x11.XInternAtom(x11_display, "_NET_WM_STATE", false) - fullscreen := x11.XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", false) - xev.type = .ClientMessage - xev.xclient.window = x11_window - xev.xclient.message_type = wm_state - xev.xclient.format = 32 - xev.xclient.data.l[0] = 0 // _NET_WM_STATE_REMOVE - xev.xclient.data.l[1] = cast(int)fullscreen - xev.xclient.data.l[2] = 0 - x11.XSendEvent(x11_display, x11.XDefaultRootWindow(x11_display), false, - {.SubstructureNotify, .SubstructureRedirect}, &xev) - - // restore the resizing constraint as well as the pre-fullscreen window size - // when returning from fullscreen - size_hints := x11.XAllocSizeHints() - - if (size_hints != nil) { - size_hints.flags = {.PPosition, .PSize} - size_hints.width = cast(i32)zephr_ctx.window.pre_fullscreen_size.x - size_hints.height = cast(i32)zephr_ctx.window.pre_fullscreen_size.y - if (zephr_ctx.window.non_resizable) { - size_hints.flags |= {.PMinSize, .PMaxSize} - size_hints.min_width = cast(i32)zephr_ctx.window.pre_fullscreen_size.x - size_hints.min_height = cast(i32)zephr_ctx.window.pre_fullscreen_size.y - size_hints.max_width = cast(i32)zephr_ctx.window.pre_fullscreen_size.x - size_hints.max_height = cast(i32)zephr_ctx.window.pre_fullscreen_size.y - } - x11.XSetWMNormalHints(x11_display, x11_window, size_hints) - x11.XFree(size_hints) - } -} - -@private -x11_toggle_fullscreen :: proc(fullscreen: bool) { - if fullscreen { - x11_return_fullscreen() - } else { - x11_go_fullscreen() - } -} - -@private -x11_assign_window_icon :: proc(icon_path: cstring, window_title: cstring) { - icon_width, icon_height: i32 - icon_data := image.load(icon_path, &icon_width, &icon_height, nil, 4) - defer image.image_free(icon_data) - assert(icon_data != nil, "Failed to load icon image") - - target_size := 2 + icon_width * icon_height - - data := make([]u64, target_size) - - // first two elements are width and height - data[0] = cast(u64)icon_width - data[1] = cast(u64)icon_height - - for i in 0..<(icon_width * icon_height) { - data[i + 2] = (cast(u64)icon_data[i * 4] << 16) | (cast(u64)icon_data[i * 4 + 1] << 8) | (cast(u64)icon_data[i * 4 + 2] << 0) | (cast(u64)icon_data[i * 4 + 3] << 24) - } - - net_wm_icon := x11.XInternAtom(x11_display, "_NET_WM_ICON", false) - x11.XChangeProperty(x11_display, x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, raw_data(data), target_size) -} - -@private -x11_get_screen_size :: proc() -> Vec2 { - screen := x11.XDefaultScreenOfDisplay(x11_display) - - return Vec2{cast(f32)screen.width, cast(f32)screen.height} -} - -@private -x11_resize_window :: proc() { - win_attrs : x11.XWindowAttributes - x11.XGetWindowAttributes(x11_display, x11_window, &win_attrs) - gl.Viewport(0, 0, win_attrs.width, win_attrs.height) -} - -@private -x11_create_window :: proc(window_title: cstring, window_size: Vec2, icon_path: cstring, window_non_resizable: bool) { - context.logger = logger - x11_display = x11.XOpenDisplay(nil) - - if x11_display == nil { - log.error("Failed to open X11 display") - return - } - - screen_num := x11.XDefaultScreen(x11_display) - root := x11.XRootWindow(x11_display, screen_num) - visual := x11.XDefaultVisual(x11_display, screen_num) - - x11_colormap = x11.XCreateColormap(x11_display, root, visual, x11.ColormapAlloc.AllocNone) - - attributes: x11.XSetWindowAttributes - attributes.event_mask = {.Exposure, .KeyPress, .KeyRelease, - .StructureNotify, .ButtonPress, .ButtonRelease, .PointerMotion} - attributes.colormap = x11_colormap - - screen := x11.XDefaultScreenOfDisplay(x11_display) - - window_start_x := screen.width / 2 - cast(i32)window_size.x / 2 - window_start_y := screen.height / 2 - cast(i32)window_size.y / 2 - - x11_window = x11.XCreateWindow(x11_display, root, window_start_x, window_start_y, cast(u32)window_size.x, cast(u32)window_size.y, 0, - x11.XDefaultDepth(x11_display), .InputOutput, visual, - {.CWColormap, .CWEventMask}, &attributes) - - if (icon_path != "") { - x11_assign_window_icon(icon_path, window_title) - } - - // Hints to the WM that the window is a normal window - // Of course this is only a hint and the WM can ignore it - net_wm_window_type := x11.XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", false) - net_wm_window_type_normal := x11.XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", false) - x11.XChangeProperty(x11_display, x11_window, net_wm_window_type, XA_ATOM, 32, PropModeReplace, &net_wm_window_type_normal, 1) - - wm_delete_window := x11.XInternAtom(x11_display, "WM_DELETE_WINDOW", false) - x11.XSetWMProtocols(x11_display, x11_window, &wm_delete_window, 1) - zephr_ctx.window_delete_atom = wm_delete_window - - // set window name - { - UTF8_STRING := x11.XInternAtom(x11_display, "UTF8_STRING", false) - x11.XStoreName(x11_display, x11_window, window_title) - text_property: x11.XTextProperty - text_property.value = raw_data(string(window_title)) - text_property.format = 8 - text_property.encoding = UTF8_STRING - text_property.nitems = len(window_title) - x11.XSetWMName(x11_display, x11_window, &text_property) - net_wm_name := x11.XInternAtom(x11_display, "_NET_WM_NAME", false) - wm_class := x11.XInternAtom(x11_display, "WM_CLASS", false) - x11.XChangeProperty(x11_display, x11_window, net_wm_name, UTF8_STRING, 8, PropModeReplace, raw_data(string(window_title)), cast(i32)len(window_title)) - x11.XChangeProperty(x11_display, x11_window, wm_class, XA_STRING, 8, PropModeReplace, raw_data(string(window_title)), cast(i32)len(window_title)) - - // name to be displayed when the window is reduced to an icon - net_wm_icon_name := x11.XInternAtom(x11_display, "_NET_WM_ICON_NAME", false) - x11.XChangeProperty(x11_display, x11_window, net_wm_icon_name, UTF8_STRING, 8, PropModeReplace, raw_data(string(window_title)), cast(i32)len(window_title)) - - text_property.encoding = XA_STRING - x11.XSetWMIconName(x11_display, x11_window, &text_property) - - class_hint := x11.XAllocClassHint() - - if (class_hint != nil) { - class_hint.res_name = window_title - class_hint.res_class = window_title - x11.XSetClassHint(x11_display, x11_window, class_hint) - x11.XFree(class_hint) - } - } - - size_hints := x11.XAllocSizeHints() - - if (size_hints != nil) { - size_hints.flags = {.PPosition, .PSize} - /* size_hints->win_gravity = StaticGravity; */ - size_hints.x = window_start_x - size_hints.y = window_start_y - size_hints.width = cast(i32)window_size.x - size_hints.height = cast(i32)window_size.y - if (window_non_resizable) { - size_hints.flags |= {.PMinSize, .PMaxSize} - size_hints.min_width = cast(i32)window_size.x - size_hints.min_height = cast(i32)window_size.y - size_hints.max_width = cast(i32)window_size.x - size_hints.max_height = cast(i32)window_size.y - } - x11.XSetWMNormalHints(x11_display, x11_window, size_hints) - x11.XFree(size_hints) - } - - x11.XMapWindow(x11_display, x11_window) - - gl.load_up_to(3, 3, proc(p: rawptr, name: cstring) { (cast(^rawptr)p)^ = glx.GetProcAddressARB(raw_data(string(name))) }) - - glx_major : i32 - glx_minor : i32 - glx.QueryVersion(x11_display, &glx_major, &glx_minor) - - log.infof("Loaded GLX: %d.%d", glx_major, glx_minor) - - visual_attributes := []i32 { - glx.RENDER_TYPE, glx.RGBA_BIT, - glx.RED_SIZE, 8, - glx.GREEN_SIZE, 8, - glx.BLUE_SIZE, 8, - glx.ALPHA_SIZE, 8, - glx.DEPTH_SIZE, 24, - glx.DOUBLEBUFFER, 1, - glx.SAMPLES, 4, // MSAA - x11.None, - } - - num_fbc: i32 - fbc := glx.ChooseFBConfig(x11_display, screen_num, raw_data(visual_attributes), &num_fbc) - - context_attributes := []i32 { - glx.CONTEXT_MAJOR_VERSION_ARB, 3, - glx.CONTEXT_MINOR_VERSION_ARB, 3, - glx.CONTEXT_PROFILE_MASK_ARB, glx.CONTEXT_CORE_PROFILE_BIT_ARB, - x11.None, - } - - glx_context = glx.CreateContextAttribsARB(x11_display, fbc[0], nil, true, raw_data(context_attributes)) - - if glx_context == nil { - log.error("Failed to create GLX context") - return - } - - glx.MakeCurrent(x11_display, x11_window, glx_context) - - gl_version := gl.GetString(gl.VERSION) - - log.infof("GL Version: %s", gl_version) - - glx.SwapIntervalEXT(x11_display, x11_window, 1) - // we enable blending for text - gl.Enable(gl.BLEND) - gl.Enable(gl.MULTISAMPLE) - gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) - x11_resize_window() - - x11.XFree(fbc) -} - -@private -x11_init :: proc(window_title: cstring, window_size: Vec2, icon_path: cstring, window_non_resizable: bool) { - x11_create_window(window_title, window_size, icon_path, window_non_resizable) -} - -@private -x11_close :: proc() { - glx.MakeCurrent(x11_display, 0, nil) - glx.DestroyContext(x11_display, glx_context) - - x11.XDestroyWindow(x11_display, x11_window) - x11.XFreeColormap(x11_display, x11_colormap) - x11.XCloseDisplay(x11_display) -} - - //////////////////////////// // // Zephr @@ -957,7 +652,7 @@ init :: proc(font_path: cstring, icon_path: cstring, window_title: cstring, wind //int res = audio_init(); //CORE_ASSERT(res == 0, "Failed to initialize audio"); - x11_init(window_title, window_size, icon_path, window_non_resizable) + backend_init(window_title, window_size, icon_path, window_non_resizable) ui_init(font_path) @@ -967,22 +662,22 @@ init :: proc(font_path: cstring, icon_path: cstring, window_title: cstring, wind zephr_ctx.window.non_resizable = window_non_resizable zephr_ctx.projection = orthographic_projection_2d(0, window_size.x, window_size.y, 0) - zephr_ctx.cursors[.ARROW] = x11.XCreateFontCursor(x11_display, .XC_left_ptr) - zephr_ctx.cursors[.IBEAM] = x11.XCreateFontCursor(x11_display, .XC_xterm) - zephr_ctx.cursors[.CROSSHAIR] = x11.XCreateFontCursor(x11_display, .XC_crosshair) - zephr_ctx.cursors[.HAND] = x11.XCreateFontCursor(x11_display, .XC_hand1) - zephr_ctx.cursors[.HRESIZE] = x11.XCreateFontCursor(x11_display, .XC_sb_h_double_arrow) - zephr_ctx.cursors[.VRESIZE] = x11.XCreateFontCursor(x11_display, .XC_sb_v_double_arrow) + // zephr_ctx.cursors[.ARROW] = x11.XCreateFontCursor(x11_display, .XC_left_ptr) + // zephr_ctx.cursors[.IBEAM] = x11.XCreateFontCursor(x11_display, .XC_xterm) + // zephr_ctx.cursors[.CROSSHAIR] = x11.XCreateFontCursor(x11_display, .XC_crosshair) + // zephr_ctx.cursors[.HAND] = x11.XCreateFontCursor(x11_display, .XC_hand1) + // zephr_ctx.cursors[.HRESIZE] = x11.XCreateFontCursor(x11_display, .XC_sb_h_double_arrow) + // zephr_ctx.cursors[.VRESIZE] = x11.XCreateFontCursor(x11_display, .XC_sb_v_double_arrow) // non-standard cursors - zephr_ctx.cursors[.DISABLED] = xcursor.LibraryLoadCursor(x11_display, "crossed_circle") + // zephr_ctx.cursors[.DISABLED] = xcursor.LibraryLoadCursor(x11_display, "crossed_circle") - zephr_ctx.screen_size = x11_get_screen_size() + zephr_ctx.screen_size = backend_get_screen_size() start_internal_timer() } deinit :: proc() { - x11_close() + backend_shutdown() //audio_close() } @@ -1027,153 +722,157 @@ swap_buffers :: proc() { zephr_ctx.ui.hovered_element = 0 } - glx.SwapBuffers(x11_display, x11_window) - x11.XDefineCursor(x11_display, x11_window, zephr_ctx.cursors[zephr_ctx.cursor]) + backend_swapbuffers() + //x11.XDefineCursor(x11_display, x11_window, zephr_ctx.cursors[zephr_ctx.cursor]) } iter_events :: proc(e_out: ^Event) -> bool { - context.logger = logger - xev: x11.XEvent - - for (cast(bool)x11.XPending(x11_display)) { - x11.XNextEvent(x11_display, &xev) - - if xev.type == .ConfigureNotify { - xce := xev.xconfigure - - if (xce.width != cast(i32)zephr_ctx.window.size.x || xce.height != cast(i32)zephr_ctx.window.size.y) { - zephr_ctx.window.size = Vec2{cast(f32)xce.width, cast(f32)xce.height} - zephr_ctx.projection = orthographic_projection_2d(0, zephr_ctx.window.size.x, zephr_ctx.window.size.y, 0) - x11_resize_window() - - e_out.type = .WINDOW_RESIZED - e_out.window.width = cast(u32)xce.width - e_out.window.height = cast(u32)xce.height - - return true - } - } else if xev.type == .DestroyNotify { - // window destroy event - e_out.type = .WINDOW_CLOSED - - return true - } else if xev.type == .ClientMessage { - // window close event - if (cast(x11.Atom)xev.xclient.data.l[0] == zephr_ctx.window_delete_atom) { - e_out.type = .WINDOW_CLOSED - - return true - } - } else if xev.type == .KeyPress { - xke := xev.xkey - - evdev_keycode := xke.keycode - 8 - scancode := evdev_scancode_to_zephr_scancode_map[evdev_keycode] - - e_out.type = .KEY_PRESSED - e_out.key.scancode = scancode - //e_out.key.code = keycode; - e_out.key.mods = x11_mods_to_zephr_mods(scancode, true) - - return true - } else if xev.type == .KeyRelease { - xke := xev.xkey - - evdev_keycode := xke.keycode - 8 - scancode := evdev_scancode_to_zephr_scancode_map[evdev_keycode] - - e_out.type = .KEY_RELEASED - e_out.key.scancode = scancode - //e_out.key.code = keycode; - e_out.key.mods = x11_mods_to_zephr_mods(scancode, false) - - return true - } else if xev.type == .ButtonPress { - e_out.type = .MOUSE_BUTTON_PRESSED - e_out.mouse.pos = Vec2{cast(f32)xev.xbutton.x, cast(f32)xev.xbutton.y} - zephr_ctx.mouse.pressed = true - - switch (xev.xbutton.button) { - case .Button1: - e_out.mouse.button = .BUTTON_LEFT - zephr_ctx.mouse.button = .BUTTON_LEFT - case .Button2: - e_out.mouse.button = .BUTTON_MIDDLE - zephr_ctx.mouse.button = .BUTTON_MIDDLE - case .Button3: - e_out.mouse.button = .BUTTON_RIGHT - zephr_ctx.mouse.button = .BUTTON_RIGHT - case .Button4: - e_out.type = .MOUSE_SCROLL - e_out.mouse.scroll_direction = .UP - case .Button5: - e_out.type = .MOUSE_SCROLL - e_out.mouse.scroll_direction = .DOWN - case cast(x11.MouseButton)8: // Back - e_out.mouse.button = .BUTTON_BACK - zephr_ctx.mouse.button = .BUTTON_BACK - case cast(x11.MouseButton)9: // Forward - e_out.mouse.button = .BUTTON_FORWARD - zephr_ctx.mouse.button = .BUTTON_FORWARD - case: - log.warnf("Unknown mouse button pressed: %d", xev.xbutton.button) - } - - return true - } else if xev.type == .ButtonRelease { - e_out.type = .MOUSE_BUTTON_RELEASED - e_out.mouse.pos = Vec2{cast(f32)xev.xbutton.x, cast(f32)xev.xbutton.y} - zephr_ctx.mouse.released = true - zephr_ctx.mouse.pressed = false - - switch (xev.xbutton.button) { - case .Button1: - e_out.mouse.button = .BUTTON_LEFT - case .Button2: - e_out.mouse.button = .BUTTON_MIDDLE - case .Button3: - e_out.mouse.button = .BUTTON_RIGHT - case .Button4: - e_out.type = .MOUSE_SCROLL - e_out.mouse.scroll_direction = .UP - case .Button5: - e_out.type = .MOUSE_SCROLL - e_out.mouse.scroll_direction = .DOWN - case cast(x11.MouseButton)8: // Back - e_out.mouse.button = .BUTTON_BACK - zephr_ctx.mouse.button = .BUTTON_BACK - case cast(x11.MouseButton)9: // Forward - e_out.mouse.button = .BUTTON_FORWARD - zephr_ctx.mouse.button = .BUTTON_FORWARD - } - - return true - } else if xev.type == .MappingNotify { - // input device mapping changed - if (xev.xmapping.request != .MappingKeyboard) { - break - } - x11.XRefreshKeyboardMapping(&xev.xmapping) - /* x11_keyboard_map_update(); */ - break - } else if xev.type == .MotionNotify { - e_out.type = .MOUSE_MOVED - e_out.mouse.pos = Vec2{cast(f32)xev.xmotion.x, cast(f32)xev.xmotion.y} - zephr_ctx.mouse.pos = e_out.mouse.pos - - return true - } - } - - return false + return backend_get_os_events(e_out) } +// iter_events :: proc(e_out: ^Event) -> bool { +// context.logger = logger +// xev: x11.XEvent + +// for (cast(bool)x11.XPending(x11_display)) { +// x11.XNextEvent(x11_display, &xev) + +// if xev.type == .ConfigureNotify { +// xce := xev.xconfigure + +// if (xce.width != cast(i32)zephr_ctx.window.size.x || xce.height != cast(i32)zephr_ctx.window.size.y) { +// zephr_ctx.window.size = Vec2{cast(f32)xce.width, cast(f32)xce.height} +// zephr_ctx.projection = orthographic_projection_2d(0, zephr_ctx.window.size.x, zephr_ctx.window.size.y, 0) +// x11_resize_window() + +// e_out.type = .WINDOW_RESIZED +// e_out.window.width = cast(u32)xce.width +// e_out.window.height = cast(u32)xce.height + +// return true +// } +// } else if xev.type == .DestroyNotify { +// // window destroy event +// e_out.type = .WINDOW_CLOSED + +// return true +// } else if xev.type == .ClientMessage { +// // window close event +// if (cast(x11.Atom)xev.xclient.data.l[0] == zephr_ctx.window_delete_atom) { +// e_out.type = .WINDOW_CLOSED + +// return true +// } +// } else if xev.type == .KeyPress { +// xke := xev.xkey + +// evdev_keycode := xke.keycode - 8 +// scancode := evdev_scancode_to_zephr_scancode_map[evdev_keycode] + +// e_out.type = .KEY_PRESSED +// e_out.key.scancode = scancode +// //e_out.key.code = keycode; +// e_out.key.mods = x11_mods_to_zephr_mods(scancode, true) + +// return true +// } else if xev.type == .KeyRelease { +// xke := xev.xkey + +// evdev_keycode := xke.keycode - 8 +// scancode := evdev_scancode_to_zephr_scancode_map[evdev_keycode] + +// e_out.type = .KEY_RELEASED +// e_out.key.scancode = scancode +// //e_out.key.code = keycode; +// e_out.key.mods = x11_mods_to_zephr_mods(scancode, false) + +// return true +// } else if xev.type == .ButtonPress { +// e_out.type = .MOUSE_BUTTON_PRESSED +// e_out.mouse.pos = Vec2{cast(f32)xev.xbutton.x, cast(f32)xev.xbutton.y} +// zephr_ctx.mouse.pressed = true + +// switch (xev.xbutton.button) { +// case .Button1: +// e_out.mouse.button = .BUTTON_LEFT +// zephr_ctx.mouse.button = .BUTTON_LEFT +// case .Button2: +// e_out.mouse.button = .BUTTON_MIDDLE +// zephr_ctx.mouse.button = .BUTTON_MIDDLE +// case .Button3: +// e_out.mouse.button = .BUTTON_RIGHT +// zephr_ctx.mouse.button = .BUTTON_RIGHT +// case .Button4: +// e_out.type = .MOUSE_SCROLL +// e_out.mouse.scroll_direction = .UP +// case .Button5: +// e_out.type = .MOUSE_SCROLL +// e_out.mouse.scroll_direction = .DOWN +// case cast(x11.MouseButton)8: // Back +// e_out.mouse.button = .BUTTON_BACK +// zephr_ctx.mouse.button = .BUTTON_BACK +// case cast(x11.MouseButton)9: // Forward +// e_out.mouse.button = .BUTTON_FORWARD +// zephr_ctx.mouse.button = .BUTTON_FORWARD +// case: +// log.warnf("Unknown mouse button pressed: %d", xev.xbutton.button) +// } + +// return true +// } else if xev.type == .ButtonRelease { +// e_out.type = .MOUSE_BUTTON_RELEASED +// e_out.mouse.pos = Vec2{cast(f32)xev.xbutton.x, cast(f32)xev.xbutton.y} +// zephr_ctx.mouse.released = true +// zephr_ctx.mouse.pressed = false + +// switch (xev.xbutton.button) { +// case .Button1: +// e_out.mouse.button = .BUTTON_LEFT +// case .Button2: +// e_out.mouse.button = .BUTTON_MIDDLE +// case .Button3: +// e_out.mouse.button = .BUTTON_RIGHT +// case .Button4: +// e_out.type = .MOUSE_SCROLL +// e_out.mouse.scroll_direction = .UP +// case .Button5: +// e_out.type = .MOUSE_SCROLL +// e_out.mouse.scroll_direction = .DOWN +// case cast(x11.MouseButton)8: // Back +// e_out.mouse.button = .BUTTON_BACK +// zephr_ctx.mouse.button = .BUTTON_BACK +// case cast(x11.MouseButton)9: // Forward +// e_out.mouse.button = .BUTTON_FORWARD +// zephr_ctx.mouse.button = .BUTTON_FORWARD +// } + +// return true +// } else if xev.type == .MappingNotify { +// // input device mapping changed +// if (xev.xmapping.request != .MappingKeyboard) { +// break +// } +// x11.XRefreshKeyboardMapping(&xev.xmapping) +// /* x11_keyboard_map_update(); */ +// break +// } else if xev.type == .MotionNotify { +// e_out.type = .MOUSE_MOVED +// e_out.mouse.pos = Vec2{cast(f32)xev.xmotion.x, cast(f32)xev.xmotion.y} +// zephr_ctx.mouse.pos = e_out.mouse.pos + +// return true +// } +// } + +// return false +// } + get_window_size :: proc() -> Vec2 { return zephr_ctx.window.size } toggle_fullscreen :: proc() { - x11_toggle_fullscreen(zephr_ctx.window.is_fullscreen) + backend_toggle_fullscreen(zephr_ctx.window.is_fullscreen) zephr_ctx.window.is_fullscreen = !zephr_ctx.window.is_fullscreen } @@ -1205,94 +904,6 @@ fnv_hash32 :: proc(data: []byte, size: u32, hash: u32) -> u32 { return hash } -@private -x11_mods_to_zephr_mods :: proc(scancode: Scancode, is_press: bool) -> KeyMod { - mods := zephr_ctx.keyboard.mods - - if (is_press) { - if (scancode == .LEFT_SHIFT) { - mods |= {.LEFT_SHIFT, .SHIFT} - } - if (scancode == .RIGHT_SHIFT) { - mods |= {.RIGHT_SHIFT, .SHIFT} - } - if (scancode == .LEFT_CTRL) { - mods |= {.LEFT_CTRL, .CTRL} - } - if (scancode == .RIGHT_CTRL) { - mods |= {.RIGHT_CTRL, .CTRL} - } - if (scancode == .LEFT_ALT) { - mods |= {.LEFT_ALT, .ALT} - } - if (scancode == .RIGHT_ALT) { - mods |= {.RIGHT_ALT, .ALT} - } - if (scancode == .LEFT_META) { - mods |= {.LEFT_META, .META} - } - if (scancode == .RIGHT_META) { - mods |= {.RIGHT_META, .META} - } - if (scancode == .CAPS_LOCK) { - mods |= {.CAPS_LOCK} - } - if (scancode == .NUM_LOCK_OR_CLEAR) { - mods |= {.NUM_LOCK} - } - } else { - if (scancode == .LEFT_SHIFT) { - mods &= ~{.LEFT_SHIFT} - } - if (scancode == .RIGHT_SHIFT) { - mods &= ~{.RIGHT_SHIFT} - } - if (!(.RIGHT_SHIFT in mods) && !(.LEFT_SHIFT in mods)) { - mods &= ~{.SHIFT} - } - - if (scancode == .LEFT_CTRL) { - mods &= ~{.LEFT_CTRL} - } - if (scancode == .RIGHT_CTRL) { - mods &= ~{.RIGHT_CTRL} - } - if (!(.RIGHT_CTRL in mods) && !(.LEFT_CTRL in mods)) { - mods &= ~{.CTRL} - } - - if (scancode == .LEFT_ALT) { - mods &= ~{.LEFT_ALT} - } - if (scancode == .RIGHT_ALT) { - mods &= ~{.RIGHT_ALT} - } - if (!(.RIGHT_ALT in mods) && !(.LEFT_ALT in mods)) { - mods &= ~{.ALT} - } - - if (scancode == .LEFT_META) { - mods &= ~{.LEFT_META} - } - if (scancode == .RIGHT_META) { - mods &= ~{.RIGHT_META} - } - if (!(.RIGHT_META in mods) && !(.LEFT_META in mods)) { - mods &= ~{.META} - } - - if (scancode == .CAPS_LOCK) { - mods &= ~{.CAPS_LOCK} - } - if (scancode == .NUM_LOCK_OR_CLEAR) { - mods &= ~{.NUM_LOCK} - } - } - - zephr_ctx.keyboard.mods = mods - return mods -} - @private logger_init :: proc() { buf : [128]byte @@ -1308,5 +919,4 @@ logger_init :: proc() { term_logger := log.create_console_logger(opt = TerminalLoggerOpts) logger = log.create_multi_logger(file_logger, term_logger) -} - +} \ No newline at end of file