diff --git a/shf/.gitignore b/shf/.gitignore new file mode 100644 index 0000000..b2b14bd --- /dev/null +++ b/shf/.gitignore @@ -0,0 +1,4 @@ +shf-glfw +shf-rgfw +shf-tests +exceptions.log diff --git a/shf/.gitmodules b/shf/.gitmodules new file mode 100644 index 0000000..e109555 --- /dev/null +++ b/shf/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test/Railroad"] + path = test/Railroad + url = https://github.com/PerplexSystems/Railroad diff --git a/shf/Makefile b/shf/Makefile new file mode 100644 index 0000000..b9f527e --- /dev/null +++ b/shf/Makefile @@ -0,0 +1,11 @@ +rgfw-debug: + ./build-unix-rgfw-debug-.sh && ./shf-rgfw + +glfw-debug: + ./build-unix-glfw-debug.sh && ./shf-glfw + +glfw: + ./build-unix-glfw.sh && ./shf-glfw + +tests: + mlton -const "Exn.keepHistory true" shf-tests.mlb && ./shf-tests diff --git a/shf/build-unix-glfw-debug.sh b/shf/build-unix-glfw-debug.sh new file mode 100755 index 0000000..bd063ca --- /dev/null +++ b/shf/build-unix-glfw-debug.sh @@ -0,0 +1,7 @@ +#!/bin/sh +mlton -const 'Exn.keepHistory true' -link-opt "$(pkg-config --cflags glfw3) $(pkg-config --static --libs glfw3)" \ + -export-header ffi/mlton-glfw-export.h \ + shf-glfw.mlb \ + ffi/glad.c \ + ffi/glfw-export.c \ + ffi/glfw-input.c diff --git a/shf/build-unix-glfw.sh b/shf/build-unix-glfw.sh new file mode 100755 index 0000000..1224ade --- /dev/null +++ b/shf/build-unix-glfw.sh @@ -0,0 +1,7 @@ +#!/bin/sh +mlton -link-opt "$(pkg-config --cflags glfw3) $(pkg-config --static --libs glfw3)" \ + -export-header ffi/mlton-glfw-export.h \ + shf-glfw.mlb \ + ffi/glad.c \ + ffi/glfw-export.c \ + ffi/glfw-input.c diff --git a/shf/build-unix-rgfw-debug-.sh b/shf/build-unix-rgfw-debug-.sh new file mode 100755 index 0000000..3594dd5 --- /dev/null +++ b/shf/build-unix-rgfw-debug-.sh @@ -0,0 +1,4 @@ +#!/bin/sh +mlton -const 'Exn.keepHistory true' -link-opt "-lX11 -lXrandr -lGL" \ + shf-rgfw.mlb \ + ffi/rgfw-export.c diff --git a/shf/fcore.mlb b/shf/fcore.mlb new file mode 100644 index 0000000..d6d5982 --- /dev/null +++ b/shf/fcore.mlb @@ -0,0 +1,61 @@ +$(SML_LIB)/basis/basis.mlb + +(* LIBRARIES (purely functional) *) +../brolib-sml/src/line_gap.sml +../cozette-sml/fonts-with-z-index/cozette-ascii.mlb + +(* FUNCTIONAL CORE *) +fcore/bin-search.sml + +ann + "allowVectorExps true" +in + fcore/persistent-vector.sml +end +fcore/search-list/dfa-gen.sml +fcore/search-list/search-list.sml + +message-types/input-msg.sml +message-types/draw-msg.sml +message-types/mailbox-type.sml + +fcore/app-type.sml +fcore/normal-mode/normal-mode-with.sml +fcore/normal-mode/normal-search-mode-with.sml +fcore/app-with.sml + +fcore/text-constants.sml +ann + "allowVectorExps true" +in + fcore/cursor-dfa/make-dfa-loop.sml + fcore/cursor-dfa/vi-word-dfa.sml + fcore/cursor-dfa/vi-caps-word-dfa.sml + fcore/cursor-dfa/vi-dlr-dfa.sml + fcore/cursor-dfa/vi-l-dfa.sml + fcore/cursor-dfa/vi-h-dfa.sml + fcore/rect.sml + fcore/pipe-cursor.sml +end +fcore/text-builder/text-builder-utils.sml +fcore/text-builder/text-builder-with-cursor.sml +fcore/text-builder/text-builder-with-highlight.sml +fcore/text-builder/normal-mode-text-builder.sml +fcore/text-builder/search-bar.sml + +fcore/cursor.sml +fcore/text-scroll.sml + +fcore/normal-mode/normal-finish.sml +fcore/normal-mode/normal-search-finish.sml +fcore/move.sml + +fcore/normal-mode/normal-move.sml +fcore/normal-mode/normal-yank.sml +fcore/normal-mode/make-normal-delete.sml +fcore/normal-mode/normal-delete.sml +fcore/normal-mode/normal-yank-delete.sml +fcore/normal-mode/normal-mode.sml +fcore/normal-mode/normal-search-mode.sml + +fcore/app-update.sml diff --git a/shf/fcore/app-type.sml b/shf/fcore/app-type.sml new file mode 100644 index 0000000..b9be7a6 --- /dev/null +++ b/shf/fcore/app-type.sml @@ -0,0 +1,45 @@ +structure AppType = +struct + datatype mode = + NORMAL_MODE of string + | NORMAL_SEARCH_MODE of + { searchString: string + , tempSearchList: PersistentVector.t + , searchCursorIdx: int + , searchScrollColumn: int + , caseSensitive: bool + } + + type app_type = + { mode: mode + , buffer: LineGap.t + , bufferModifyTime: Time.time + , searchList: PersistentVector.t + , windowWidth: int + , windowHeight: int + (* line to start drawing from *) + , startLine: int + (* absolute index of movable cursor *) + , cursorIdx: int + (* column to start drawing text at for horizontal scrolling. *) + , visualScrollColumn: int + , dfa: int vector vector + (* msgs to send after an update. + * The list of messages is reset on each invocation of AppUpdate.update. *) + , msgs: MailboxType.t list + } + + fun init (buffer, windowWidth, windowHeight, time) : app_type = + { mode = NORMAL_MODE "" + , buffer = buffer + , bufferModifyTime = time + , searchList = PersistentVector.empty + , windowWidth = windowWidth + , windowHeight = windowHeight + , startLine = 0 + , cursorIdx = 0 + , visualScrollColumn = 0 + , dfa = Vector.fromList [] + , msgs = [] + } +end diff --git a/shf/fcore/app-update.sml b/shf/fcore/app-update.sml new file mode 100644 index 0000000..bfedb0a --- /dev/null +++ b/shf/fcore/app-update.sml @@ -0,0 +1,10 @@ +structure AppUpdate = +struct + open AppType + + fun update (app: app_type, msg, time) = + case #mode app of + NORMAL_MODE modeData => NormalMode.update (app, modeData, msg, time) + | NORMAL_SEARCH_MODE modeData => + NormalSearchMode.update (app, modeData, msg, time) +end diff --git a/shf/fcore/app-with.sml b/shf/fcore/app-with.sml new file mode 100644 index 0000000..3c691e7 --- /dev/null +++ b/shf/fcore/app-with.sml @@ -0,0 +1,35 @@ +structure AppWith = +struct + open AppType + + (* this function exists only for testing *) + fun idx (app, newIdx) = + let + val + { startLine + , buffer + , bufferModifyTime + , searchList + , dfa + , mode + , windowWidth + , windowHeight + , msgs + , visualScrollColumn + , cursorIdx = _ + } = app + in + { startLine = startLine + , buffer = buffer + , bufferModifyTime = bufferModifyTime + , searchList = searchList + , dfa = dfa + , mode = mode + , windowWidth = windowWidth + , windowHeight = windowHeight + , msgs = msgs + , visualScrollColumn = visualScrollColumn + , cursorIdx = newIdx + } + end +end diff --git a/shf/fcore/bin-search.sml b/shf/fcore/bin-search.sml new file mode 100644 index 0000000..3bda1cc --- /dev/null +++ b/shf/fcore/bin-search.sml @@ -0,0 +1,120 @@ +structure BinSearch = +struct + local + fun reverseLinearSearch (findNum, idx, vec) = + if idx < 0 then + ~1 + else + let + val curVal = Vector.sub (vec, idx) + in + if curVal < findNum then idx + else reverseLinearSearch (findNum, idx - 1, vec) + end + + fun helpBinSearch (findNum, vec, low, high) = + let + val mid = low + ((high - low) div 2) + in + if high >= low then + let + val midVal = Vector.sub (vec, mid) + in + if midVal = findNum then + mid + else if midVal < findNum then + helpBinSearch (findNum, vec, mid + 1, high) + else + helpBinSearch (findNum, vec, low, mid - 1) + end + else + reverseLinearSearch (findNum, mid, vec) + end + in + fun equalOrLess (findNum, vec) = + helpBinSearch (findNum, vec, 0, Vector.length vec - 1) + end + + local + fun forwardLinearSearch (findNum, idx, vec) = + if idx = Vector.length vec then + ~1 + else + let + val curVal = Vector.sub (vec, idx) + in + if curVal > findNum then idx + else forwardLinearSearch (findNum, idx + 1, vec) + end + + fun helpBinSearch (findNum, vec, low, high) = + let + val mid = low + ((high - low) div 2) + in + if high >= low then + let + val midVal = Vector.sub (vec, mid) + in + if midVal = findNum then + mid + else if midVal < findNum then + helpBinSearch (findNum, vec, mid + 1, high) + else + helpBinSearch (findNum, vec, low, mid - 1) + end + else + forwardLinearSearch (findNum, Int.max (mid, 0), vec) + end + in + fun equalOrMore (findNum, vec) = + helpBinSearch (findNum, vec, 0, Vector.length vec - 1) + end + + local + fun helpExists (findNum, vec, low, high) = + let + val mid = low + ((high - low) div 2) + in + if high >= low then + let + val midVal = Vector.sub (vec, mid) + in + if midVal = findNum then + true + else if midVal < findNum then + helpExists (findNum, vec, mid + 1, high) + else + helpExists (findNum, vec, low, mid - 1) + end + else + false + end + in + fun exists (findNum, vec) = + helpExists (findNum, vec, 0, Vector.length vec - 1) + end + + local + fun helpEqualOrMinus1 (findNum, vec, low, high) = + let + val mid = low + ((high - low) div 2) + in + if high >= low then + let + val midVal = Vector.sub (vec, mid) + in + if midVal = findNum then + mid + else if midVal < findNum then + helpEqualOrMinus1 (findNum, vec, mid + 1, high) + else + helpEqualOrMinus1 (findNum, vec, low, mid - 1) + end + else + ~1 + end + in + fun equalOrMinus1 (findNum, vec) = + helpEqualOrMinus1 (findNum, vec, 0, Vector.length vec - 1) + end +end diff --git a/shf/fcore/cursor-dfa/make-dfa-loop.sml b/shf/fcore/cursor-dfa/make-dfa-loop.sml new file mode 100644 index 0000000..0a7a9d1 --- /dev/null +++ b/shf/fcore/cursor-dfa/make-dfa-loop.sml @@ -0,0 +1,303 @@ +signature MAKE_DFA_LOOP = +sig + val fStart: int * int * string * string list * Word8.word * int -> int + val startState: Word8.word +end + +functor MakeNextDfaLoop(M: MAKE_DFA_LOOP) = +struct + fun next (lineGap: LineGap.t, cursorIdx, count) = + let + val {rightStrings, idx = bufferIdx, ...} = lineGap + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + case rightStrings of + shd :: stl => + if strIdx < String.size shd then + (* strIdx is in this string *) + M.fStart (strIdx, cursorIdx, shd, stl, M.startState, count) + else + (* strIdx is in tl *) + (case stl of + stlhd :: stltl => + let + val strIdx = strIdx - String.size shd + in + M.fStart + (strIdx, cursorIdx, stlhd, stltl, M.startState, count) + end + | _ => cursorIdx) + | [] => cursorIdx + end +end + +functor MakeNextDfaLoopPlus1(M: MAKE_DFA_LOOP) = +struct + fun next (lineGap: LineGap.t, cursorIdx, count) = + let + val {rightStrings, idx = bufferIdx, ...} = lineGap + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + 1 + val absIdx = cursorIdx + 1 + in + case rightStrings of + shd :: stl => + if strIdx < String.size shd then + (* strIdx is in this string *) + M.fStart (strIdx, absIdx, shd, stl, M.startState, count) + else + (* strIdx is in tl *) + (case stl of + stlhd :: stltl => + let val strIdx = strIdx - String.size shd + in M.fStart (strIdx, absIdx, stlhd, stltl, M.startState, count) + end + | _ => cursorIdx) + | [] => cursorIdx + end +end + +functor MakePrevDfaLoop(M: MAKE_DFA_LOOP) = +struct + fun startLeftStrings (leftStrings, absIdx, count) = + case leftStrings of + lhd :: ltl => + M.fStart (String.size lhd - 1, absIdx, lhd, ltl, M.startState, count) + | [] => 0 + + fun prev (lineGap: LineGap.t, cursorIdx, count) = + let + val {rightStrings, leftStrings, idx = bufferIdx, ...} = lineGap + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + case rightStrings of + shd :: stl => + if strIdx < String.size shd then + (* strIdx is in this string *) + M.fStart (strIdx, cursorIdx, shd, leftStrings, M.startState, count) + else + (* strIdx is in tl *) + (case stl of + stlhd :: stltl => + let + val strIdx = strIdx - String.size shd + val leftStrings = shd :: leftStrings + in + M.fStart + ( strIdx + , cursorIdx + , stlhd + , leftStrings + , M.startState + , count + ) + end + | [] => startLeftStrings (leftStrings, cursorIdx, count)) + | [] => startLeftStrings (leftStrings, cursorIdx, count) + end +end + +functor MakePrevDfaLoopMinus1(M: MAKE_DFA_LOOP) = +struct + fun startLeftStrings (leftStrings, absIdx, count) = + case leftStrings of + lhd :: ltl => + M.fStart (String.size lhd - 1, absIdx, lhd, ltl, M.startState, count) + | [] => 0 + + fun prev (lineGap: LineGap.t, cursorIdx, count) = + let + val {idx = bufferIdx, leftStrings, ...} = lineGap + val strIdx = cursorIdx - bufferIdx - 1 + val absIdx = cursorIdx - 1 + in + if strIdx < 0 then + startLeftStrings (leftStrings, absIdx, count) + else + case #rightStrings lineGap of + rhd :: _ => + M.fStart (strIdx, absIdx, rhd, leftStrings, M.startState, count) + | [] => startLeftStrings (leftStrings, absIdx, count) + end +end + +signature MAKE_CHAR_FOLDER = +sig + val startState: Word8.word + val tables: Word8.word vector vector + + val isFinal: Word8.word -> bool + val finish: int -> int +end + +functor MakeCharFolderNext(Fn: MAKE_CHAR_FOLDER) = +struct + fun nextState (currentState, currentChar) = + let + val currentState = Word8.toInt currentState + val currentTable = Vector.sub (Fn.tables, currentState) + val charIdx = Char.ord currentChar + in + Vector.sub (currentTable, charIdx) + end + + fun foldNext (idx, absIdx, str, tl, currentState, counter) = + if idx = String.size str then + case tl of + str :: tl => foldNext (0, absIdx, str, tl, currentState, counter) + | [] => absIdx + else + let + val chr = String.sub (str, idx) + val newState = nextState (currentState, chr) + in + if Fn.isFinal newState then + if counter - 1 = 0 then + Fn.finish absIdx + else + (* new loop, so reset start state and proceed *) + let val newState = nextState (Fn.startState, chr) + in foldNext (idx + 1, absIdx + 1, str, tl, newState, counter - 1) + end + else + foldNext (idx + 1, absIdx + 1, str, tl, newState, counter) + end +end + +functor MakeCharFolderPrev(Fn: MAKE_CHAR_FOLDER) = +struct + fun nextState (currentState, currentChar) = + let + val currentState = Word8.toInt currentState + val currentTable = Vector.sub (Fn.tables, currentState) + val charIdx = Char.ord currentChar + in + Vector.sub (currentTable, charIdx) + end + + fun foldPrev (idx, absIdx, str, tl, currentState, counter) = + if idx < 0 then + case tl of + str :: tl => + foldPrev (String.size str - 1, absIdx, str, tl, currentState, counter) + | [] => 0 + else + let + val chr = String.sub (str, idx) + val newState = nextState (currentState, chr) + in + if Fn.isFinal newState then + if counter - 1 = 0 then + Fn.finish absIdx + else + let val newState = nextState (Fn.startState, chr) + in foldPrev (idx - 1, absIdx - 1, str, tl, newState, counter - 1) + end + else + foldPrev (idx - 1, absIdx - 1, str, tl, newState, counter) + end +end + +signature MAKE_IF_CHAR_FOLDER = +sig + type env + + val fStart: + int * string * int vector * int * string list * int vector list * env + -> int +end + +functor MakeIfCharFolderPrev(Fn: MAKE_IF_CHAR_FOLDER) = +struct + fun foldPrev (lineGap: LineGap.t, cursorIdx, env: Fn.env) = + let + val + {rightStrings, idx = bufferIdx, rightLines, leftStrings, leftLines, ...} = + lineGap + in + case (rightStrings, rightLines) of + (strHd :: strTl, lnHd :: lnTl) => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size strHd then + (* strIdx is either in this string or in leftStrings *) + if strIdx < 0 then + case (leftStrings, leftLines) of + (lshd :: lstl, llhd :: lltl) => + Fn.fStart + ( String.size lshd - 1 + , lshd + , llhd + , cursorIdx + , lstl + , lltl + , env + ) + | (_, _) => 0 + else + Fn.fStart + (strIdx, strHd, lnHd, cursorIdx, leftStrings, leftLines, env) + else + (* strIdx must be in the strTl *) + (case (strTl, lnTl) of + (nestStrHd :: _, nestLnHd :: _) => + let + val strIdx = strIdx - String.size strHd + in + Fn.fStart + ( strIdx + , nestStrHd + , nestLnHd + , cursorIdx + , strHd :: leftStrings + , lnHd :: leftLines + , env + ) + end + | (_, _) => cursorIdx) + end + | (_, _) => (* nowhere to go, so return cursorIdx *) cursorIdx + end +end + +functor MakeIfCharFolderNext(Fn: MAKE_IF_CHAR_FOLDER) = +struct + fun foldNext (lineGap: LineGap.t, cursorIdx, env: Fn.env) = + let + val {rightStrings, idx = bufferIdx, rightLines, ...} = lineGap + in + case (rightStrings, rightLines) of + (strHd :: strTl, lnHd :: lnTl) => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size strHd then + (* strIdx is in this string *) + Fn.fStart (strIdx, strHd, lnHd, cursorIdx, strTl, lnTl, env) + else + (* strIdx must be in the strTl *) + (case (strTl, lnTl) of + (nestStrHd :: nestStrTl, nestLnHd :: nestLnTl) => + let + val strIdx = strIdx - String.size strHd + in + Fn.fStart + ( strIdx + , nestStrHd + , nestLnHd + , cursorIdx + , nestStrTl + , nestLnTl + , env + ) + end + | (_, _) => cursorIdx) + end + | (_, _) => (* nowhere to go, so return cursorIdx *) cursorIdx + end +end diff --git a/shf/fcore/cursor-dfa/vi-caps-word-dfa.sml b/shf/fcore/cursor-dfa/vi-caps-word-dfa.sml new file mode 100644 index 0000000..1373225 --- /dev/null +++ b/shf/fcore/cursor-dfa/vi-caps-word-dfa.sml @@ -0,0 +1,239 @@ +structure ViCapsWordDfa = +struct + val startState: Word8.word = 0w0 + val startNonBlankState: Word8.word = 0w1 + val startSpaceState: Word8.word = 0w2 + val nonBlankAfterSpaceState: Word8.word = 0w3 + val spaceAfterNonBlankState = 0w4 + val startNewline: Word8.word = 0w5 + val newlineToNewline: Word8.word = 0w6 + val chrToNewline: Word8.word = 0w07 + + fun makeStart i = + let + val chr = Char.chr i + in + if chr = #"\n" then startNewline + else if Char.isSpace chr then startSpaceState + else startNonBlankState + end + + fun makeStartNonBlankState i = + let + val chr = Char.chr i + in + if chr = #"\n" then chrToNewline + else if Char.isSpace chr then spaceAfterNonBlankState + else startNonBlankState + end + + fun makeStartSpace i = + let + val chr = Char.chr i + in + if chr = #"\n" then chrToNewline + else if Char.isSpace chr then startSpaceState + else nonBlankAfterSpaceState + end + + fun makeNonBlankAfterSpace i = + let + val chr = Char.chr i + in + if chr = #"\n" then chrToNewline + else if Char.isSpace chr then spaceAfterNonBlankState + else nonBlankAfterSpaceState + end + + fun makeStartNewline i = + let + val chr = Char.chr i + in + if chr = #"\n" then newlineToNewline + else if Char.isSpace chr then startSpaceState + else nonBlankAfterSpaceState + end + + val startTable = Vector.tabulate (255, makeStart) + val startNonBlankTable = Vector.tabulate (255, makeStartNonBlankState) + val startSpaceTable = Vector.tabulate (255, makeStartSpace) + val nonBlankAfterSpaceTable = Vector.tabulate (255, makeNonBlankAfterSpace) + val spaceAfterNonBlankTable = nonBlankAfterSpaceTable + val newlineTable = Vector.tabulate (255, makeStartNewline) + + val tables = + #[ startTable + , startNonBlankTable + , startSpaceTable + , nonBlankAfterSpaceTable + , spaceAfterNonBlankTable + , newlineTable + , newlineTable + , newlineTable + ] + + structure StartOfNextWORD = + MakeNextDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + fun finish x = x + + fun isFinal currentState = + currentState = nonBlankAfterSpaceState + orelse currentState = newlineToNewline + end) + + val fStart = Folder.foldNext + end) + + structure StartOfNextWORDForDelete = + MakeNextDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + fun finish x = x + + fun isFinal currentState = + currentState = nonBlankAfterSpaceState + orelse currentState = chrToNewline + end) + + val fStart = Folder.foldNext + end) + + structure EndOfPrevWORD = + MakePrevDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderPrev + (struct + val startState = startState + val tables = tables + + fun finish x = x + + fun isFinal currentState = + currentState = nonBlankAfterSpaceState + end) + + val fStart = Folder.foldPrev + end) + + structure StartOfCurrentWORDFolder = + MakeCharFolderPrev + (struct + val startState = startState + val tables = tables + + fun isFinal currentState = + currentState = spaceAfterNonBlankState + orelse currentState = chrToNewline + orelse currentState = newlineToNewline + + fun finish idx = idx + 1 + end) + + structure StartOfCurrentWORD = + MakePrevDfaLoopMinus1 + (struct + val startState = startState + val fStart = StartOfCurrentWORDFolder.foldPrev + end) + + structure StartOfNextWORDStrict = + MakePrevDfaLoop + (struct + val startState = startState + val fStart = StartOfCurrentWORDFolder.foldPrev + end) + + fun endOfCurrentWORDFolderIsFinal currentState = + currentState = spaceAfterNonBlankState orelse currentState = chrToNewline + + structure EndOfCurrentWORDFolder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + val isFinal = endOfCurrentWORDFolderIsFinal + + fun finish idx = + Int.max (0, idx - 1) + end) + + structure EndOfCurrentWORD = + MakeNextDfaLoopPlus1 + (struct + val startState = startState + val fStart = EndOfCurrentWORDFolder.foldNext + end) + + structure EndOfCurrentWORDForDelete = + MakeNextDfaLoopPlus1 + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + val isFinal = endOfCurrentWORDFolderIsFinal + + fun finish idx = idx + end) + + val fStart = Folder.foldNext + end) + + structure EndOfCurrentWORDStrict = + MakeNextDfaLoop + (struct + val startState = startState + val fStart = EndOfCurrentWORDFolder.foldNext + end) + + (* W *) + val startOfNextWORD = StartOfNextWORD.next + val startOfNextWORDForDelete = StartOfNextWORDForDelete.next + (* gE *) + val endOfPrevWORD = EndOfPrevWORD.prev + (* B *) + val startOfCurrentWORD = StartOfCurrentWORD.prev + (* E *) + val endOfCurrentWORD = EndOfCurrentWORD.next + val endOfCurrentWORDForDelete = EndOfCurrentWORDForDelete.next + + (* functions to strictly get the start and end of the current word. + * Problem: We want to support Vi motions like viW (selects a single word), + * as well as ciW (change one WORD) and diW (delete one WORD). + * + * The 'startOfCurrentWORD' and 'endOfCurrentWORD' functions do this + * (representing the vi 'B' and 'E' commands respectively), + * except that 'B' goes to the previous WORD if the cursor is on the first + * character of the current WORD, and 'E' goes to the next WORD if the cursor + * is on the last character of the current WORD. + * + * What is meant by "strict" is that these below functions always stay + * within the current WORD, not making the two exceptions mentioned above. + *) + val startOfCurrentWORDStrict = StartOfNextWORDStrict.prev + val endOfCurrentWORDStrict = EndOfCurrentWORDStrict.next +end diff --git a/shf/fcore/cursor-dfa/vi-dlr-dfa.sml b/shf/fcore/cursor-dfa/vi-dlr-dfa.sml new file mode 100644 index 0000000..b4a9b33 --- /dev/null +++ b/shf/fcore/cursor-dfa/vi-dlr-dfa.sml @@ -0,0 +1,64 @@ +structure ViDlrDfa = +struct + val startState: Word8.word = 0w0 + val newlineState: Word8.word = 0w1 + val notNewlineState = 0w2 + + fun makeStart i = + if Char.chr i = #"\n" then newlineState else notNewlineState + + val startTable = Vector.tabulate (255, makeStart) + val newlineTable = startTable + val notNewlineTable = startTable + + val tables = #[startTable, newlineTable, notNewlineTable] + + fun isFinal currentState = currentState = newlineState + + structure ViDlr = + MakeNextDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + fun finish x = x - 1 + val isFinal = isFinal + end) + + fun fStart (idx, absIdx, str, tl, currentState, counter) = + if String.sub (str, idx) = #"\n" then + if counter = 1 then + absIdx + else + Folder.foldNext + (idx + 1, absIdx + 1, str, tl, currentState, counter - 1) + else + Folder.foldNext (idx, absIdx, str, tl, currentState, counter) + end) + + structure ViDlrForDelete = + MakeNextDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + fun finish x = x + 1 + val isFinal = isFinal + end) + + val fStart = Folder.foldNext + end) + + val next = ViDlr.next + val nextForDelete = ViDlrForDelete.next +end diff --git a/shf/fcore/cursor-dfa/vi-h-dfa.sml b/shf/fcore/cursor-dfa/vi-h-dfa.sml new file mode 100644 index 0000000..a1b9f83 --- /dev/null +++ b/shf/fcore/cursor-dfa/vi-h-dfa.sml @@ -0,0 +1,76 @@ +structure ViHDfa = +struct + val startState: Word8.word = 0w0 + val oneNewlineState: Word8.word = 0w1 + val twoNewlineState: Word8.word = 0w2 + val chrState: Word8.word = 0w3 + val chrBeforeNewlieState: Word8.word = 0w4 + + fun makeStart i = + if Char.chr i = #"\n" then oneNewlineState else chrState + + fun makeOneNewline i = + if Char.chr i = #"\n" then twoNewlineState else chrBeforeNewlieState + + val startTable = Vector.tabulate (255, makeStart) + val oneNewlineTable = Vector.tabulate (255, makeOneNewline) + val twoNewlineTable = oneNewlineTable + val chrTable = startTable + val chrBeforeNewlieTable = startTable + + val tables = + #[ startTable + , oneNewlineTable + , twoNewlineTable + , chrTable + , chrBeforeNewlieTable + ] + + fun next (currentState, chr) = + let val table = Vector.sub (tables, Word8.toInt currentState) + in Vector.sub (table, Char.ord chr) + end + + structure ViH = + MakePrevDfaLoop + (struct + val startState = startState + + fun loop (idx, absIdx, str, tl, currentState, counter) = + if idx < 0 then + case tl of + str :: tl => + loop + (String.size str - 1, absIdx, str, tl, currentState, counter) + | [] => 0 + else + let + val chr = String.sub (str, idx) + val newState = next (currentState, chr) + in + if newState = chrBeforeNewlieState orelse newState = chrState then + if counter - 1 = ~1 then + absIdx + else + loop (idx - 1, absIdx - 1, str, tl, startState, counter - 1) + else if newState = twoNewlineState then + if counter - 1 = ~1 then + absIdx + 1 + else + loop + ( idx - 1 + , absIdx - 1 + , str + , tl + , oneNewlineState + , counter - 1 + ) + else + loop (idx - 1, absIdx - 1, str, tl, newState, counter) + end + + val fStart = loop + end) + + val prev = ViH.prev +end diff --git a/shf/fcore/cursor-dfa/vi-l-dfa.sml b/shf/fcore/cursor-dfa/vi-l-dfa.sml new file mode 100644 index 0000000..48cb613 --- /dev/null +++ b/shf/fcore/cursor-dfa/vi-l-dfa.sml @@ -0,0 +1,53 @@ +structure ViLDfa = +struct + val startState: Word8.word = 0w0 + val newlineState: Word8.word = 0w1 + val chrState: Word8.word = 0w2 + val newlineAfterCHrState: Word8.word = 0w3 + + fun makeStart i = + if Char.chr i = #"\n" then newlineState else chrState + + fun makeChr i = + if Char.chr i = #"\n" then newlineAfterCHrState else chrState + + val startTable = Vector.tabulate (255, makeStart) + val newlineTable = startTable + val chrTable = Vector.tabulate (255, makeChr) + val newlineAfterCHrTable = startTable + + val tables = #[startTable, newlineTable, chrTable, newlineAfterCHrTable] + + fun next (currentState, chr) = + let val table = Vector.sub (tables, Word8.toInt currentState) + in Vector.sub (table, Char.ord chr) + end + + structure ViL = + MakeNextDfaLoop + (struct + val startState = startState + + fun loop (idx, absIdx, str, tl, currentState, counter) = + if idx = String.size str then + case tl of + str :: tl => loop (0, absIdx, str, tl, currentState, counter) + | [] => absIdx + else + let + val chr = String.sub (str, idx) + val newState = next (currentState, chr) + in + if newState = newlineAfterCHrState then + loop (idx + 1, absIdx + 1, str, tl, newState, counter) + else if counter - 1 = ~1 then + absIdx + else + loop (idx + 1, absIdx + 1, str, tl, newState, counter - 1) + end + + val fStart = loop + end) + + val next = ViL.next +end diff --git a/shf/fcore/cursor-dfa/vi-word-dfa.sml b/shf/fcore/cursor-dfa/vi-word-dfa.sml new file mode 100644 index 0000000..cf5260f --- /dev/null +++ b/shf/fcore/cursor-dfa/vi-word-dfa.sml @@ -0,0 +1,285 @@ +structure ViWordDfa = +struct + val startState: Word8.word = 0w0 + + val startAlpha: Word8.word = 0w1 + val startSpace: Word8.word = 0w2 + val startPunct: Word8.word = 0w3 + + val alphaToSpace: Word8.word = 0w4 + val punctToSpace: Word8.word = 0w5 + + val spaceToAlpha: Word8.word = 0w6 + val spaceToPunct: Word8.word = 0w7 + + val startNewline: Word8.word = 0w8 + val newlineToNewline: Word8.word = 0w9 + val chrToNewline: Word8.word = 0w10 + + val newlineToAlpha: Word8.word = 0w11 + val newlineToPunct: Word8.word = 0w12 + + val alphaToPunct: Word8.word = 0w13 + val punctToAlpha: Word8.word = 0w14 + + fun makeStart i = + let + val chr = Char.chr i + in + if Char.isAlphaNum chr orelse chr = #"_" then startAlpha + else if chr = #"\n" then startNewline + else if Char.isSpace chr then startSpace + else startPunct + end + + fun makeStartAlpha i = + let + val chr = Char.chr i + in + if Char.isAlphaNum chr orelse chr = #"_" then startAlpha + else if chr = #"\n" then chrToNewline + else if Char.isSpace chr then alphaToSpace + else alphaToPunct + end + + fun makeStartSpace i = + let + val chr = Char.chr i + in + if Char.isAlphaNum chr orelse chr = #"_" then spaceToAlpha + else if chr = #"\n" then chrToNewline + else if Char.isSpace chr then startSpace + else spaceToPunct + end + + fun makeStartPunct i = + let + val chr = Char.chr i + in + if Char.isAlphaNum chr orelse chr = #"_" then punctToAlpha + else if chr = #"\n" then chrToNewline + else if Char.isSpace chr then punctToSpace + else startPunct + end + + fun makeStartNewline i = + let + val chr = Char.chr i + in + if Char.isAlphaNum chr orelse chr = #"_" then newlineToAlpha + else if chr = #"\n" then newlineToNewline + else if Char.isSpace chr then startSpace + else newlineToPunct + end + + val startTable = Vector.tabulate (255, makeStart) + + val startAlphaTable = Vector.tabulate (255, makeStartAlpha) + val startSpaceTable = Vector.tabulate (255, makeStartSpace) + val startPunctTable = Vector.tabulate (255, makeStartPunct) + + val alphaToSpaceTable = startSpaceTable + val punctToSpaceTable = startSpaceTable + + val spaceToAlphaTable = startAlphaTable + val spaceToPunctTable = startPunctTable + + val newlineTable = Vector.tabulate (255, makeStartNewline) + + val tables = + #[ startTable + + , startAlphaTable + , startSpaceTable + , startPunctTable + + , alphaToSpaceTable + , punctToSpaceTable + + , spaceToAlphaTable + , spaceToPunctTable + + , newlineTable + , newlineTable + , newlineTable + + , startAlphaTable + , startPunctTable + ] + + structure StartOfNextWord = + MakeNextDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + fun isFinal currentState = + currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = spaceToAlpha + orelse currentState = spaceToPunct + orelse currentState = newlineToNewline + orelse currentState = newlineToAlpha + orelse currentState = newlineToPunct + + fun finish x = x + end) + + val fStart = Folder.foldNext + end) + + (* This is the same as StartOfNextWord, except for the `isFinal` function. + * The difference is that the `isFinal` function here considers + * the state where any character goes to a newline, + * to be a final state. + * This is because, in Vim, the 'w' motion will move past a newline + * when that newline is preceded by a non-newline character. + * However, the 'dw' motion deletes until that newline + * (not including the newline itself). + * It is easier, less fragile, and perhaps clearer, + * to implement the difference using a transition table like this + * than convoluted if-statements. *) + structure StartOfNextWordForDelete = + MakeNextDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + fun isFinal currentState = + currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = spaceToAlpha + orelse currentState = spaceToPunct + orelse currentState = chrToNewline + + fun finish x = x + end) + + val fStart = Folder.foldNext + end) + + structure EndOfPrevWord = + MakePrevDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderPrev + (struct + val startState = startState + val tables = tables + + fun isFinal currentState = + currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = spaceToAlpha + orelse currentState = spaceToPunct + orelse currentState = newlineToNewline + orelse currentState = newlineToAlpha + orelse currentState = newlineToPunct + + fun finish x = x + end) + + val fStart = Folder.foldPrev + end) + + structure StartOfCurrentWordFolder = + MakeCharFolderPrev + (struct + val startState = startState + val tables = tables + + fun isFinal currentState = + currentState = alphaToSpace orelse currentState = punctToSpace + orelse currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = chrToNewline + orelse currentState = newlineToNewline + + fun finish idx = idx + 1 + end) + + structure StartOfCurrentWord = + MakePrevDfaLoopMinus1 + (struct + val startState = startState + val fStart = StartOfCurrentWordFolder.foldPrev + end) + + structure StartOfCurrentWordStrict = + MakePrevDfaLoop + (struct + val startState = startState + val fStart = StartOfCurrentWordFolder.foldPrev + end) + + fun isFinalForEndOfCurrentWord currentState = + currentState = alphaToSpace orelse currentState = punctToSpace + orelse currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = chrToNewline + + structure EndOfCurrentWordFolder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + val isFinal = isFinalForEndOfCurrentWord + fun finish x = x - 1 + end) + + structure EndOfCurrentWord = + MakeNextDfaLoopPlus1 + (struct + val startState = startState + val fStart = EndOfCurrentWordFolder.foldNext + end) + + structure EndOfCurrentWordStrict = + MakeNextDfaLoop + (struct + val startState = startState + val fStart = EndOfCurrentWordFolder.foldNext + end) + + structure EndOfCurrentWordForDelete = + MakeNextDfaLoopPlus1 + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + val isFinal = isFinalForEndOfCurrentWord + fun finish x = x + end) + + val fStart = Folder.foldNext + end) + + (* w *) + val startOfNextWord = StartOfNextWord.next + val startOfNextWordForDelete = StartOfNextWordForDelete.next + (* ge *) + val endOfPrevWord = EndOfPrevWord.prev + (* b *) + val startOfCurrentWord = StartOfCurrentWord.prev + (* e *) + val endOfCurrentWord = EndOfCurrentWord.next + val endOfCurrentWordForDelete = EndOfCurrentWordForDelete.next + + (* the meaning of "Strict" and the utility of these two functions + * is described in vi-WORD-dfa.sml *) + val startOfCurrentWordStrict = StartOfCurrentWordStrict.prev + val endOfCurrentWordStrict = EndOfCurrentWordStrict.next +end diff --git a/shf/fcore/cursor.sml b/shf/fcore/cursor.sml new file mode 100644 index 0000000..9bc5d31 --- /dev/null +++ b/shf/fcore/cursor.sml @@ -0,0 +1,1028 @@ +structure Cursor = +struct + (* returns absolute index of previous line break in string *) + fun helpVi0 (absIdx, stl, ltl) = + case (stl, ltl) of + (shd :: stl, lhd :: ltl) => + if Vector.length lhd > 0 then + let + val startAbsIdx = absIdx - String.size shd + val lineIdx = Vector.sub (lhd, Vector.length lhd - 1) + in + (* found lineIdx. + * Need to make sure we follow cursor-on-linebreak rule: + * If line break is preceded by non-line break char, + * then increment by 1. + * *) + startAbsIdx + lineIdx + 1 + end + else + helpVi0 (absIdx - String.size shd, stl, ltl) + | (_, _) => 0 + + fun startVi0 (strPos, shd, lhd, absIdx, stl, ltl) = + if String.sub (shd, strPos) = #"\n" then + absIdx + else if Vector.length lhd > 0 then + if Vector.sub (lhd, 0) < strPos then + let + val linePos = BinSearch.equalOrLess (strPos - 1, lhd) + val lineIdx = Vector.sub (lhd, linePos) + in + if linePos = ~1 then + (* no previous line in lhd *) + helpVi0 (absIdx - strPos, stl, ltl) + else + let val lineIdx = Vector.sub (lhd, linePos) + in absIdx - strPos + lineIdx + 1 + end + end + else + helpVi0 (absIdx - strPos, stl, ltl) + else + helpVi0 (absIdx - strPos, stl, ltl) + + structure Vi0 = + MakeIfCharFolderPrev + (struct + type env = unit + fun fStart (strPos, shd, lhd, absIdx, stl, ltl, _) = + startVi0 (strPos, shd, lhd, absIdx, stl, ltl) + end) + + fun vi0 (lineGap, cursorIdx) = Vi0.foldPrev (lineGap, cursorIdx, ()) + + val viDlr = ViDlrDfa.next + val viDlrForDelete = ViDlrDfa.nextForDelete + + val viL = ViLDfa.next + val viH = ViHDfa.prev + + (* equivalent of vi's 'w' command *) + val nextWord = ViWordDfa.startOfNextWord + val nextWordForDelete = ViWordDfa.startOfNextWordForDelete + + (* equivalent of vi's 'W' command *) + val nextWORD = ViCapsWordDfa.startOfNextWORD + val nextWORDForDelete = ViCapsWordDfa.startOfNextWORDForDelete + + (* equivalent of vi's 'b' command *) + val prevWord = ViWordDfa.startOfCurrentWord + val prevWordStrict = ViWordDfa.startOfCurrentWordStrict + + (* equivalent of vi's 'B' command *) + val prevWORD = ViCapsWordDfa.startOfCurrentWORD + val prevWORDStrict = ViCapsWordDfa.startOfCurrentWORDStrict + + (* equivalent of vi's 'ge' command *) + val endOfPrevWord = ViWordDfa.endOfPrevWord + + (* equivalent of vi's 'gE' command *) + val endOfPrevWORD = ViCapsWordDfa.endOfPrevWORD + + (* equivalent of vi's `e` command *) + val endOfWord = ViWordDfa.endOfCurrentWord + val endOfWordForDelete = ViWordDfa.endOfCurrentWordForDelete + val endOfWordStrict = ViWordDfa.endOfCurrentWordStrict + + (* equivalent of vi's `E` command *) + val endOfWORD = ViCapsWordDfa.endOfCurrentWORD + val endOfWORDForDelete = ViCapsWordDfa.endOfCurrentWORDForDelete + val endOfWORDStrict = ViCapsWordDfa.endOfCurrentWORDStrict + + (* Prerequisite: + * LineGap has been moved to start of line (provided with vi0). *) + structure FirstNonSpaceChr = + MakeIfCharFolderPrev + (struct + type env = unit + + fun helpFirstNonSpaceChr (strPos, str, absIdx, stl) = + if strPos = String.size str then + case stl of + shd :: stl => helpFirstNonSpaceChr (0, shd, absIdx, stl) + | [] => absIdx - 1 + else + let + val chr = String.sub (str, strPos) + in + if chr = #" " then + helpFirstNonSpaceChr (strPos + 1, str, absIdx + 1, stl) + else + absIdx + end + + fun fStart (strIdx, shd, _, absIdx, stl, _, _) = + if strIdx < String.size shd then + helpFirstNonSpaceChr (strIdx, shd, absIdx, stl) + else + case stl of + stlhd :: stltl => helpFirstNonSpaceChr (0, stlhd, absIdx, stltl) + | [] => (* tl is empty; just return absIdx *) absIdx + end) + + fun firstNonSpaceChr (lineGap, cursorIdx) = + FirstNonSpaceChr.foldPrev (lineGap, cursorIdx, ()) + + structure ToNextChr = + MakeIfCharFolderNext + (struct + type env = {findChr: char, count: int} + + fun helpToNextChr + (strPos, str, absIdx, stl, lastFoundIdx, findChr, count) = + if strPos = String.size str then + case stl of + str :: stl => + helpToNextChr + (0, str, absIdx, stl, lastFoundIdx, findChr, count) + | [] => lastFoundIdx + else + let + val chr = String.sub (str, strPos) + in + if chr = findChr then + if count - 1 = 0 then + absIdx + else + helpToNextChr + ( strPos + 1 + , str + , absIdx + 1 + , stl + , absIdx + , findChr + , count - 1 + ) + else + helpToNextChr + ( strPos + 1 + , str + , absIdx + 1 + , stl + , lastFoundIdx + , findChr + , count + ) + end + + fun fStart (strPos, str, _, absIdx, stl, _, {findChr, count}) = + (* we want to start iterating from char after cursor *) + helpToNextChr (strPos + 1, str, absIdx + 1, stl, ~1, findChr, count) + end) + + val toNextChr = ToNextChr.foldNext + + structure ToPrevChr = + MakeIfCharFolderPrev + (struct + type env = {findChr: char, count: int} + + fun helpToPrevChr + (strPos, str, absIdx, stl, lastFoundIdx, findChr, count) = + if strPos < 0 then + case stl of + shd :: stl => + helpToPrevChr + ( String.size shd - 1 + , shd + , absIdx + , stl + , lastFoundIdx + , findChr + , count + ) + | [] => lastFoundIdx + else if String.sub (str, strPos) = findChr then + if count - 1 = 0 then + absIdx + else + helpToPrevChr + (strPos - 1, str, absIdx - 1, stl, absIdx, findChr, count - 1) + else + helpToPrevChr + (strPos - 1, str, absIdx - 1, stl, lastFoundIdx, findChr, count) + + fun fStart (strIdx, shd, _, absIdx, stl, _, {findChr, count}) = + helpToPrevChr (strIdx - 1, shd, absIdx - 1, stl, ~1, findChr, count) + end) + + val toPrevChr = ToPrevChr.foldPrev + + structure ToOpenChrPrev = + MakeIfCharFolderPrev + (struct + type env = {openChr: char, closeChr: char} + + fun loop (strPos, str, absIdx, stl, openChr, closeChr, closeCount) = + if strPos < 0 then + case stl of + str :: stl => + loop + ( String.size str - 1 + , str + , absIdx + , stl + , openChr + , closeChr + , closeCount + ) + | [] => ~1 + else + let + val chr = String.sub (str, strPos) + in + if chr = openChr then + if closeCount = 0 then + absIdx + else + loop + ( strPos - 1 + , str + , absIdx - 1 + , stl + , openChr + , closeChr + , closeCount - 1 + ) + else if chr = closeChr then + loop + ( strPos - 1 + , str + , absIdx - 1 + , stl + , openChr + , closeChr + , closeCount + 1 + ) + else + loop + ( strPos - 1 + , str + , absIdx - 1 + , stl + , openChr + , closeChr + , closeCount + ) + end + + fun fStart (strPos, str, _, absIdx, stl, _, {openChr, closeChr}) = + loop (strPos, str, absIdx, stl, openChr, closeChr, 0) + end) + + val toOpenChrPrev = ToOpenChrPrev.foldPrev + + structure NextPairChr = + MakeIfCharFolderNext + (struct + type env = unit + + fun isPairChr chr = + chr = #"(" orelse chr = #")" orelse chr = #"[" orelse chr = #"]" + orelse chr = #"{" orelse chr = #"}" orelse chr = #"<" + orelse chr = #">" + + fun loop (strPos, str, absIdx, stl) = + if strPos = String.size str then + case stl of + str :: stl => loop (0, str, absIdx, stl) + | [] => ~1 + else + let + val chr = String.sub (str, strPos) + in + if isPairChr chr then absIdx + else loop (strPos + 1, str, absIdx + 1, stl) + end + + fun fStart (strPos, str, _, absIdx, stl, _, _) = + loop (strPos, str, absIdx, stl) + end) + + fun nextPairChr (lineGap, cursorIdx) = + NextPairChr.foldNext (lineGap, cursorIdx, ()) + + fun helpMatchPairNext + (strPos, str, absIdx, stl, openChr, openNum, closeChr, closeNum) = + if strPos = String.size str then + case stl of + hd :: tl => + helpMatchPairNext + (0, hd, absIdx, tl, openChr, openNum, closeChr, closeNum) + | [] => ~1 + else + let + val chr = String.sub (str, strPos) + val openNum = if chr = openChr then openNum + 1 else openNum + val closeNum = if chr = closeChr then closeNum + 1 else closeNum + in + if openNum = closeNum then + absIdx + else + helpMatchPairNext + ( strPos + 1 + , str + , absIdx + 1 + , stl + , openChr + , openNum + , closeChr + , closeNum + ) + end + + fun helpMatchPairPrev + (strPos, str, absIdx, stl, openChr, openNum, closeChr, closeNum) = + if strPos < 0 then + case stl of + hd :: tl => + helpMatchPairPrev + ( String.size hd - 1 + , hd + , absIdx + , tl + , openChr + , openNum + , closeChr + , closeNum + ) + | [] => ~1 + else + let + val chr = String.sub (str, strPos) + val openNum = if chr = openChr then openNum + 1 else openNum + val closeNum = if chr = closeChr then closeNum + 1 else closeNum + in + if openNum = closeNum then + absIdx + else + helpMatchPairPrev + ( strPos - 1 + , str + , absIdx - 1 + , stl + , openChr + , openNum + , closeChr + , closeNum + ) + end + + fun startMatchPair (strIdx, shd, leftStrings, rightStrings, cursorIdx) = + case String.sub (shd, strIdx) of + #"(" => + helpMatchPairNext + (strIdx + 1, shd, cursorIdx + 1, rightStrings, #"(", 1, #")", 0) + | #")" => + helpMatchPairPrev + (strIdx - 1, shd, cursorIdx - 1, leftStrings, #"(", 0, #")", 1) + | #"[" => + helpMatchPairNext + (strIdx + 1, shd, cursorIdx + 1, rightStrings, #"[", 1, #"]", 0) + | #"]" => + helpMatchPairPrev + (strIdx - 1, shd, cursorIdx - 1, leftStrings, #"[", 0, #"]", 1) + | #"{" => + helpMatchPairNext + (strIdx + 1, shd, cursorIdx + 1, rightStrings, #"{", 1, #"}", 0) + | #"}" => + helpMatchPairPrev + (strIdx - 1, shd, cursorIdx - 1, leftStrings, #"{", 0, #"}", 1) + | #"<" => + helpMatchPairNext + (strIdx + 1, shd, cursorIdx + 1, rightStrings, #"<", 1, #">", 0) + | #">" => + helpMatchPairPrev + (strIdx - 1, shd, cursorIdx - 1, leftStrings, #"<", 0, #">", 1) + | _ => ~1 + + fun matchPair (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, idx = bufferIdx, leftStrings, ...} = lineGap + in + case rightStrings of + shd :: stl => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size shd then + (* strIdx is in this string *) + startMatchPair (strIdx, shd, leftStrings, stl, cursorIdx) + else + (* strIdx is in tl *) + (case stl of + stlhd :: stltl => + let + val strIdx = strIdx - String.size shd + val leftStrings = shd :: leftStrings + in + startMatchPair + (strIdx, stlhd, leftStrings, stltl, cursorIdx) + end + | [] => cursorIdx) + end + | [] => cursorIdx + end + + structure FirstContiguousAlpha = + MakeIfCharFolderPrev + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => loop (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if Char.isAlphaNum chr orelse chr = #"_" then + loop (strPos - 1, shd, absIdx - 1, stl) + else + absIdx + 1 + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun firstContiguousAlpha (lineGap, cursorIdx) = + FirstContiguousAlpha.foldPrev (lineGap, cursorIdx, ()) + + structure LastContiguousAlpha = + MakeIfCharFolderNext + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loop (0, shd, absIdx, stl) + | [] => Int.max (0, absIdx - 1) + else + let + val chr = String.sub (shd, strPos) + in + if Char.isAlphaNum chr orelse chr = #"_" then + loop (strPos + 1, shd, absIdx + 1, stl) + else + absIdx - 1 + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun lastContiguousAlpha (lineGap, cursorIdx) = + LastContiguousAlpha.foldNext (lineGap, cursorIdx, ()) + + structure FirstContiguousSpace = + MakeIfCharFolderPrev + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => loop (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if Char.isSpace chr then + if chr = #"\n" then absIdx + 1 + else loop (strPos - 1, shd, absIdx - 1, stl) + else + absIdx + 1 + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun firstContiguousSpace (lineGap, cursorIdx) = + FirstContiguousSpace.foldPrev (lineGap, cursorIdx, ()) + + structure LastContiguousSpace = + MakeIfCharFolderNext + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loop (0, shd, absIdx, stl) + | [] => Int.max (0, absIdx - 1) + else + let + val chr = String.sub (shd, strPos) + in + if Char.isSpace chr then + if chr = #"\n" then absIdx - 1 + else loop (strPos + 1, shd, absIdx + 1, stl) + else + absIdx - 1 + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun lastContiguousSpace (lineGap, cursorIdx) = + LastContiguousSpace.foldNext (lineGap, cursorIdx, ()) + + structure FirstContiguousPunct = + MakeIfCharFolderPrev + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => loop (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if Char.isAlphaNum chr orelse chr = #"_" orelse Char.isSpace chr then + absIdx + 1 + else + loop (strPos - 1, shd, absIdx - 1, stl) + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun firstContiguousPunct (lineGap, cursorIdx) = + FirstContiguousPunct.foldPrev (lineGap, cursorIdx, ()) + + structure LastContiguousPunct = + MakeIfCharFolderNext + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loop (0, shd, absIdx, stl) + | [] => Int.max (0, absIdx - 1) + else + let + val chr = String.sub (shd, strPos) + in + if Char.isAlphaNum chr orelse chr = #"_" orelse Char.isSpace chr then + absIdx - 1 + else + loop (strPos + 1, shd, absIdx + 1, stl) + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun lastContiguousPunct (lineGap, cursorIdx) = + LastContiguousPunct.foldNext (lineGap, cursorIdx, ()) + + structure FirstContiguousNonSpace = + MakeIfCharFolderPrev + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => loop (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if Char.isSpace chr then absIdx + 1 + else loop (strPos - 1, shd, absIdx - 1, stl) + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun firstContiguousNonSpace (lineGap, cursorIdx) = + FirstContiguousNonSpace.foldPrev (lineGap, cursorIdx, ()) + + structure LastContiguousNonSpace = + MakeIfCharFolderNext + (struct + type env = unit + + fun loop (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loop (0, shd, absIdx, stl) + | [] => Int.max (0, absIdx - 1) + else + let + val chr = String.sub (shd, strPos) + in + if Char.isSpace chr then absIdx - 1 + else loop (strPos + 1, shd, absIdx + 1, stl) + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + loop (strPos, shd, absIdx, stl) + end) + + fun lastContiguousNonSpace (lineGap, cursorIdx) = + LastContiguousNonSpace.foldNext (lineGap, cursorIdx, ()) + + structure AroundWordPrev = + MakeIfCharFolderPrev + (struct + type env = unit + + fun loopAlphaNum (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => + loopAlphaNum (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if Char.isAlphaNum chr orelse chr = #"_" then + loopAlphaNum (strPos - 1, shd, absIdx - 1, stl) + else + absIdx + 1 + end + + fun loopPunct (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => loopPunct (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if + Char.isAlphaNum chr orelse chr = #"_" orelse chr = #"\n" + orelse Char.isSpace chr + then absIdx + 1 + else (* is punct *) loopPunct (strPos - 1, shd, absIdx - 1, stl) + end + + fun loopSpace (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => loopSpace (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx + 1 + else if Char.isSpace chr then + loopSpace (strPos - 1, shd, absIdx - 1, stl) + else + absIdx + 1 + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx + else if Char.isAlphaNum chr orelse chr = #"_" then + loopAlphaNum (strPos - 1, shd, absIdx - 1, stl) + else if Char.isSpace chr then + loopSpace (strPos - 1, shd, absIdx - 1, stl) + else + loopPunct (strPos - 1, shd, absIdx - 1, stl) + end + end) + + fun aroundWordPrev (lineGap, cursorIdx) = + AroundWordPrev.foldPrev (lineGap, cursorIdx, ()) + + structure AroundWordNext = + MakeIfCharFolderNext + (struct + type env = unit + + fun stopAtFirstNonSpace (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => stopAtFirstNonSpace (0, shd, absIdx, stl) + | [] => absIdx + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx - 1 + else if Char.isSpace chr then + stopAtFirstNonSpace (strPos + 1, shd, absIdx + 1, stl) + else + absIdx - 1 + end + + fun loopAlphaNum (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loopAlphaNum (0, shd, absIdx, stl) + | [] => absIdx + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx - 1 + else if Char.isAlphaNum chr orelse chr = #"_" then + loopAlphaNum (strPos + 1, shd, absIdx + 1, stl) + else if Char.isSpace chr then + stopAtFirstNonSpace (strPos + 1, shd, absIdx + 1, stl) + else + (* is punct *) + absIdx - 1 + end + + fun loopPunct (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loopPunct (0, shd, absIdx, stl) + | [] => absIdx + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx - 1 + else if Char.isAlphaNum chr orelse chr = #"_" then + absIdx - 1 + else if Char.isSpace chr then + stopAtFirstNonSpace (strPos + 1, shd, absIdx + 1, stl) + else + (* is punct *) + loopPunct (strPos + 1, shd, absIdx + 1, stl) + end + + fun loopSpace (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loopSpace (0, shd, absIdx, stl) + | [] => absIdx + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx - 1 + else if Char.isAlphaNum chr orelse chr = #"_" then + loopAlphaNum (strPos + 1, shd, absIdx + 1, stl) + else if Char.isSpace chr then + loopSpace (strPos + 1, shd, absIdx + 1, stl) + else + loopPunct (strPos + 1, shd, absIdx + 1, stl) + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx + else if Char.isAlphaNum chr orelse chr = #"_" then + loopAlphaNum (strPos + 1, shd, absIdx + 1, stl) + else if Char.isSpace chr then + loopSpace (strPos + 1, shd, absIdx + 1, stl) + else + loopPunct (strPos + 1, shd, absIdx + 1, stl) + end + end) + + fun aroundWordNext (lineGap, cursorIdx) = + AroundWordNext.foldNext (lineGap, cursorIdx, ()) + + structure AroundWORDPrev = + MakeIfCharFolderPrev + (struct + type env = unit + + fun loopNonSpace (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => + loopNonSpace (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if Char.isSpace chr then absIdx + 1 + else loopNonSpace (strPos - 1, shd, absIdx - 1, stl) + end + + fun loopSpace (strPos, shd, absIdx, stl) = + if strPos < 0 then + case stl of + shd :: stl => loopSpace (String.size shd - 1, shd, absIdx, stl) + | [] => 0 + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx + 1 + else if Char.isSpace chr then + loopSpace (strPos - 1, shd, absIdx - 1, stl) + else + absIdx + 1 + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx + else if Char.isSpace chr then + loopSpace (strPos - 1, shd, absIdx - 1, stl) + else + loopNonSpace (strPos - 1, shd, absIdx - 1, stl) + end + end) + + fun aroundWORDPrev (lineGap, cursorIdx) = + AroundWORDPrev.foldPrev (lineGap, cursorIdx, ()) + + structure AroundWORDNext = + MakeIfCharFolderNext + (struct + type env = unit + + fun stopAtFirstNonSpace (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => stopAtFirstNonSpace (0, shd, absIdx, stl) + | [] => absIdx + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx - 1 + else if Char.isSpace chr then + stopAtFirstNonSpace (strPos + 1, shd, absIdx + 1, stl) + else + absIdx - 1 + end + + fun loopNonSpace (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loopNonSpace (0, shd, absIdx, stl) + | [] => absIdx + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx - 1 + else if Char.isSpace chr then + stopAtFirstNonSpace (strPos + 1, shd, absIdx + 1, stl) + else + loopNonSpace (strPos + 1, shd, absIdx + 1, stl) + end + + fun loopSpace (strPos, shd, absIdx, stl) = + if strPos = String.size shd then + case stl of + shd :: stl => loopSpace (0, shd, absIdx, stl) + | [] => absIdx + else + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx - 1 + else if Char.isSpace chr then + loopSpace (strPos + 1, shd, absIdx + 1, stl) + else + loopNonSpace (strPos + 1, shd, absIdx + 1, stl) + end + + fun fStart (strPos, shd, _, absIdx, stl, _, _) = + let + val chr = String.sub (shd, strPos) + in + if chr = #"\n" then + absIdx + else if Char.isSpace chr then + loopSpace (strPos + 1, shd, absIdx + 1, stl) + else + loopNonSpace (strPos + 1, shd, absIdx + 1, stl) + end + end) + + fun aroundWORDNext (lineGap, cursorIdx) = + AroundWORDNext.foldNext (lineGap, cursorIdx, ()) + + fun isCursorAtStartOfLine (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, idx = bufferIdx, ...} = lineGap + in + case rightStrings of + hd :: tl => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size hd then + (* chr is in hd *) + String.sub (hd, strIdx) = #"\n" + else + (* chr is in tl *) + (case tl of + tlhd :: _ => + let val strIdx = strIdx - String.size hd + in String.sub (tlhd, strIdx) = #"\n" + end + | [] => true) + end + | [] => true + end + + (* Prerequisite: lineGap is moved to cursorIdx *) + fun isPrevChrStartOfLine (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, idx = bufferIdx, leftStrings, ...} = lineGap + in + case rightStrings of + hd :: tl => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx > 0 then + (* prev chr is in hd *) + String.sub (hd, strIdx - 1) = #"\n" + else + (* prev chr if in leftStrings *) + (case leftStrings of + lhd :: _ => String.sub (lhd, String.size lhd - 1) = #"\n" + | [] => + (* cursorIdx = 0 which means we are at start of file/line *) + true) + end + | [] => true + end + + fun helpIsNextChrEndOfLine (strIdx, hd, tl) = + if strIdx + 1 < String.size hd then + (* next chr is in this string *) + String.sub (hd, strIdx + 1) = #"\n" + else + (* next chr, if it exists, is in tl *) + (case tl of + tlhd :: _ => String.sub (tlhd, 0) = #"\n" + | [] => true) + + (* Prerequisite: lineGap is moved to cursorIdx *) + fun isNextChrEndOfLine (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, idx = bufferIdx, ...} = lineGap + in + case rightStrings of + hd :: tl => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size hd then + helpIsNextChrEndOfLine (strIdx, hd, tl) + else + (* strIdx is in tl *) + (case tl of + tlhd :: tltl => + helpIsNextChrEndOfLine (strIdx - String.size hd, tlhd, tltl) + | [] => + (* strIdx is at end of lineGap + * which also means at end of line *) + true) + end + | [] => true + end + + (* Prerequisite: lineGap is moved to cursorIdx *) + fun isOnNewlineAfterChr (buffer, cursorIdx) = + cursorIdx > 0 andalso not (isPrevChrStartOfLine (buffer, cursorIdx)) + andalso isCursorAtStartOfLine (buffer, cursorIdx) +end diff --git a/shf/fcore/move.sml b/shf/fcore/move.sml new file mode 100644 index 0000000..e7c603e --- /dev/null +++ b/shf/fcore/move.sml @@ -0,0 +1,103 @@ +signature MOVE = +sig + val fMove: LineGap.t * int -> int +end + +signature MAKE_MOVE = +sig + val move: AppType.app_type * int -> AppType.app_type +end + +functor MakeMove(Fn: MOVE): MAKE_MOVE = +struct + fun finish (app: AppType.app_type, buffer, cursorIdx) = + let + val {searchList, bufferModifyTime, ...} = app + in + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, searchList, [], bufferModifyTime) + end + + fun helpMove (app: AppType.app_type, buffer, cursorIdx, count) = + if count = 0 then + finish (app, buffer, cursorIdx) + else + (* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *) + let + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val textLength = #textLength buffer + val newCursorIdx = Fn.fMove (buffer, cursorIdx) + in + if newCursorIdx >= textLength - 1 then + let val newCursorIdx = Int.max (textLength - 1, 0) + in finish (app, buffer, newCursorIdx) + end + else + helpMove (app, buffer, newCursorIdx, count - 1) + end + + fun move (app: AppType.app_type, count) = + let val {cursorIdx, buffer, ...} = app + in helpMove (app, buffer, cursorIdx, count) + end +end + +structure MoveToStartOfLine = MakeMove (struct val fMove = Cursor.vi0 end) + +signature DFA_MOVE = +sig + val fMove: LineGap.t * int * int -> int +end + +signature MAKE_DFA_MOVE = +sig + val move: AppType.app_type * int -> AppType.app_type +end + +functor MakeDfaMove(Fn: DFA_MOVE): MAKE_DFA_MOVE = +struct + fun move (app: AppType.app_type, count) : AppType.app_type = + let + val {buffer, cursorIdx, searchList, bufferModifyTime, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Fn.fMove (buffer, cursorIdx, count) + + val textLength = #textLength buffer + in + if cursorIdx >= textLength - 1 then + let + val cursorIdx = Int.max (textLength - 1, 0) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, cursorIdx) then cursorIdx - 1 + else cursorIdx + in + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, searchList, [], bufferModifyTime) + end + else + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, searchList, [], bufferModifyTime) + end +end + +structure MoveViH = MakeDfaMove (struct val fMove = Cursor.viH end) +structure MoveViL = MakeDfaMove (struct val fMove = Cursor.viL end) + +structure MoveToNextWord = MakeDfaMove (struct val fMove = Cursor.nextWord end) +structure MoveToNextWORD = MakeDfaMove (struct val fMove = Cursor.nextWORD end) + +structure MoveToEndOfWord = + MakeDfaMove (struct val fMove = Cursor.endOfWord end) +structure MoveToEndOfWORD = + MakeDfaMove (struct val fMove = Cursor.endOfWORD end) + +structure MoveToPrevWord = MakeDfaMove (struct val fMove = Cursor.prevWord end) +structure MoveToPrevWORD = MakeDfaMove (struct val fMove = Cursor.prevWORD end) + +structure MoveToEndOfPrevWord = + MakeDfaMove (struct val fMove = Cursor.endOfPrevWord end) +structure MoveToEndOfPrevWORD = + MakeDfaMove (struct val fMove = Cursor.endOfPrevWORD end) + +structure MoveToEndOfLine = MakeDfaMove (struct val fMove = Cursor.viDlr end) diff --git a/shf/fcore/normal-mode/make-normal-delete.sml b/shf/fcore/normal-mode/make-normal-delete.sml new file mode 100644 index 0000000..00e3e6a --- /dev/null +++ b/shf/fcore/normal-mode/make-normal-delete.sml @@ -0,0 +1,1298 @@ +signature MAKE_NORMAL_DELETE = +sig + val initMsgs: int * int * LineGap.t -> MailboxType.t list +end + +functor MakeNormalDelete(Fn: MAKE_NORMAL_DELETE) = +struct + open AppType + open DrawMsg + open MailboxType + + fun finishAfterDeletingBuffer (app, low, buffer, searchList, time, msgs) = + let val buffer = LineGap.goToIdx (low, buffer) + in NormalFinish.buildTextAndClear (app, buffer, low, searchList, msgs, time) + end + + fun deleteAndFinish (app: app_type, low, length, buffer, time) = + let + val {searchList, dfa, ...} = app + + val buffer = LineGap.goToIdx (low + length, buffer) + val msgs = Fn.initMsgs (low, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (low, length, buffer, searchList, dfa) + + val low = + if low >= #textLength buffer - 1 then + Int.max (#textLength buffer - 1, 0) + else + low + + val buffer = LineGap.goToIdx (low, buffer) + + val low = + if Cursor.isOnNewlineAfterChr (buffer, low) then low - 1 else low + + val buffer = + if #textLength buffer = 0 then LineGap.fromString "\n" else buffer + val buffer = LineGap.goToIdx (low, buffer) + in + NormalFinish.buildTextAndClear (app, buffer, low, searchList, msgs, time) + end + + fun moveCursorAfterDeletingLines + (app, buffer, time, initialMsg, startIdx, searchList) = + let + val newEndIdx = #textLength buffer - 1 + in + if newEndIdx < 0 then + (* deleted whole file; add newline to the end *) + let + val buffer = LineGap.append ("\n", buffer) + val (buffer, searchList) = SearchList.build (buffer, #dfa app) + in + finishAfterDeletingBuffer + (app, 0, buffer, searchList, time, initialMsg) + end + else if newEndIdx = 0 then + (* there is only one char left in the file *) + finishAfterDeletingBuffer (app, 0, buffer, searchList, time, initialMsg) + else if startIdx >= newEndIdx then + (* deleted the last part of the file such that the cursor's idx + * now refers to an index that no longer exists. + * Have to move cursor to the last line of the file. *) + let + val buffer = LineGap.goToIdx (newEndIdx, buffer) + in + if Cursor.isOnNewlineAfterChr (buffer, newEndIdx) then + let + val buffer = LineGap.goToIdx (newEndIdx - 1, buffer) + val newCursorIdx = Cursor.vi0 (buffer, newEndIdx - 1) + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + else + let + val newCursorIdx = Cursor.vi0 (buffer, newEndIdx) + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + end + else + finishAfterDeletingBuffer + (app, startIdx, buffer, searchList, time, initialMsg) + end + + (* equivalent of vi's 'x' command **) + fun removeChr (app: app_type, count, time) = + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + in + if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then + NormalFinish.clearMode app + else + let + val lineStart = Cursor.vi0 (buffer, cursorIdx) + val lineEnd = Cursor.viDlrForDelete (buffer, cursorIdx, 1) + val buffer = LineGap.goToIdx (lineEnd, buffer) + + (* if specified length (cursorIdx + count) extends + * beyond current line's length, + * then clip the length, to ensure we don't delete the newline *) + val high = cursorIdx + count + val high = + if Cursor.isOnNewlineAfterChr (buffer, lineEnd) then lineEnd + else Int.min (lineEnd - 1, high) + val length = high - cursorIdx + + val initialMsg = Fn.initMsgs (cursorIdx, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (cursorIdx, length, buffer, searchList, dfa) + + (* figure out where to place cursor *) + val buffer = LineGap.goToIdx (lineStart, buffer) + val lineEndAfterDelete = Cursor.viDlr (buffer, lineStart, 1) + + val cursorIdx = Int.min (lineEndAfterDelete, cursorIdx) + in + if cursorIdx >= #textLength buffer - 1 then + (* special case: when buffer does not end with newline + * and we are deleting from the last line *) + let + val cursorIdx = #textLength buffer - 1 + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, cursorIdx) then + cursorIdx - 1 + else + cursorIdx + in + finishAfterDeletingBuffer + (app, cursorIdx, buffer, searchList, time, initialMsg) + end + else + finishAfterDeletingBuffer + (app, cursorIdx, buffer, searchList, time, initialMsg) + end + end + + (* Note: The below implementation of removing line breaks with the 'J' + * command slightly differs from the implementation in Vim. + * In Vim, the J, 1J, and 2J commands all have the same effect. + * 3J, 4J, 5J, etc. all have different effects because of the different counts + * though. + * Our implementation has a different effect for each count. + * 1J delettes 1 line break, 2J deletes 2, and so on. *) + fun helpRemoveLineBreaks + (app, buffer, cursorIdx, count, time, searchList, dfa) = + if count = 0 then + (* we don't use Fn.initMsgs in this function. + * Removing line breaks is a discrete action which doesn't operate + * as a range the way that motions like 'dw' or 'diw'. + * Instead, a single character is deleted at different places. + * So it doesn't make any sense to use Fn.initMsgs + * which expects a range. *) + finishAfterDeletingBuffer (app, cursorIdx, buffer, searchList, time, []) + else + let + val buffer = LineGap.goToIdx (cursorIdx, buffer) + in + if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then + if cursorIdx >= #textLength buffer - 2 then + finishAfterDeletingBuffer + (app, cursorIdx, buffer, searchList, time, []) + else + (* if the cursor is at a linebreak, delete the linebreak + * and don't insert a space. *) + let + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (cursorIdx, 1, buffer, searchList, dfa) + in + helpRemoveLineBreaks + (app, buffer, cursorIdx, count - 1, time, searchList, dfa) + end + else + let + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = #"\n", count = 1}) + in + if newCursorIdx >= #textLength buffer - 2 then + finishAfterDeletingBuffer + (app, cursorIdx, buffer, searchList, time, []) + else + let + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (newCursorIdx, 1, buffer, searchList, dfa) + val (buffer, searchList) = SearchList.insert + (newCursorIdx, " ", buffer, searchList, dfa) + in + helpRemoveLineBreaks + (app, buffer, newCursorIdx, count - 1, time, searchList, dfa) + end + end + end + + fun removeLineBreaks (app: app_type, count, time) = + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + in + if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then + if cursorIdx >= #textLength buffer - 2 then + NormalFinish.clearMode app + else + let + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (cursorIdx, 1, buffer, searchList, dfa) + in + helpRemoveLineBreaks + (app, buffer, cursorIdx, count - 1, time, searchList, dfa) + end + else + let + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = #"\n", count = 1}) + val buffer = LineGap.goToIdx (newCursorIdx, buffer) + in + if + cursorIdx = newCursorIdx + orelse newCursorIdx >= #textLength buffer - 2 + then + (* no change, either because no #"\n" was found + * or because the next #"\n" found is the last character in the + * file. + * We don't delete the last #"\n" in the file + * because Unix convention is that text files always + * end with newlines. *) + NormalFinish.clearMode app + else + let + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (newCursorIdx, 1, buffer, searchList, dfa) + val (buffer, searchList) = SearchList.insert + (newCursorIdx, " ", buffer, searchList, dfa) + in + helpRemoveLineBreaks + (app, buffer, newCursorIdx, count - 1, time, searchList, dfa) + end + end + end + + fun helpDelete + (app: app_type, buffer, cursorIdx, otherIdx, count, fMove, time) = + (* As a small optimisation to reduce allocations, + * we accumulate otherIdx by calling fMove with it and the buffer + * on each loop. + * Then, at the end of the loop, we perform the actual deletion. + * This is faster than performing the actual deletion on every loop + * because we only delete once, and avoid allocating intermediary buffers. + * The behaviour between the two is equivalent. *) + if count = 0 then + let + val {searchList, dfa, ...} = app + + val low = Int.min (cursorIdx, otherIdx) + val high = Int.max (cursorIdx, otherIdx) + val high = + if high >= #textLength buffer - 2 then + Int.max (#textLength buffer - 2, 0) + else + high + val length = high - low + + val buffer = LineGap.goToIdx (high, buffer) + val initialMsg = Fn.initMsgs (low, length, buffer) + + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (low, length, buffer, searchList, dfa) + + (* If we have deleted from the buffer so that cursorIdx + * is no longer a valid idx, + * clip cursorIdx to the end. *) + val buffer = LineGap.goToIdx (low, buffer) + val cursorIdx = + if low >= #textLength buffer then + Int.max (#textLength buffer - 2, low) + else + low + in + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, searchList, initialMsg, time) + end + else + let + (* get otherIdx, where cursor will want to go after motion. *) + val buffer = LineGap.goToIdx (otherIdx, buffer) + val newOtherIdx = fMove (buffer, otherIdx) + val newCount = if newOtherIdx = otherIdx then 0 else count - 1 + in + helpDelete (app, buffer, cursorIdx, newOtherIdx, newCount, fMove, time) + end + + fun delete (app: app_type, count, fMove, time) = + let val {buffer, cursorIdx, ...} = app + in helpDelete (app, buffer, cursorIdx, cursorIdx, count, fMove, time) + end + + fun finishDeleteByDfa (app: app_type, low, high, buffer, time) = + let + val {searchList, dfa, ...} = app + + val buffer = LineGap.goToIdx (high, buffer) + val high = + (* by default, we have a newline at the end of the buffer. + * However, we also support the case where there is no newline + * at the end of the buffer. + * The first case is supported by default, + * but we have to increment the textLength if the buffer + * does not end with a newline, to make sure that + * we delete the last character. *) + if + high = #textLength buffer - 1 + andalso not (Cursor.isCursorAtStartOfLine (buffer, high)) + then #textLength buffer + else high + + val length = high - low + val initialMsg = Fn.initMsgs (low, length, buffer) + + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (low, length, buffer, searchList, dfa) + + (* if we deleted all text in the buffer, + * then make sure that we append a newline at the end + * so that the buffer contains at least one character. + * *) + val (buffer, searchList) = + if #textLength buffer = 0 then + let val buffer = LineGap.append ("\n", buffer) + in SearchList.build (buffer, dfa) + end + else + (buffer, searchList) + in + if low >= #textLength buffer - 1 andalso #textLength buffer > 0 then + (* edge case: + * when we delete from the cursor's position to the end of a file, + * we have to move the cursor to be on the last char + * because the original cursor's position is no longer valid. *) + let + val newCursorIdx = Int.max (#textLength buffer - 1, 0) + val buffer = LineGap.goToIdx (newCursorIdx, buffer) + val newCursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, newCursorIdx) then + newCursorIdx - 1 + else + newCursorIdx + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + else + let + val buffer = LineGap.goToIdx (low, buffer) + val newCursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, low) then low - 1 else low + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + end + + fun deleteWord (app as {buffer, ...}: app_type, count, time) = + if #textLength buffer = 1 then + NormalFinish.clearMode app + else + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val high = Cursor.nextWordForDelete (buffer, cursorIdx, count) + val length = high - cursorIdx + in + deleteAndFinish (app, cursorIdx, length, buffer, time) + end + + fun deleteWORD (app as {buffer, ...}: app_type, count, time) = + if #textLength buffer = 1 then + NormalFinish.clearMode app + else + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val high = Cursor.nextWORDForDelete (buffer, cursorIdx, count) + val length = high - cursorIdx + in + deleteAndFinish (app, cursorIdx, length, buffer, time) + end + + fun deleteByDfa (app as {buffer, ...}: app_type, count, fMove, time) = + if #textLength buffer = 1 then + NormalFinish.clearMode app + else + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val otherIdx = fMove (buffer, cursorIdx, count) + in + if otherIdx > cursorIdx then + if otherIdx >= #textLength buffer then + (* prevent us from deleting last newline + * to help us preserve unix-style line endings *) + let + (* if we're on the first character/column in a line, + * we would like to delete the preceding newline too *) + val cursorIdx = + if Cursor.isPrevChrStartOfLine (buffer, cursorIdx) then + cursorIdx - 1 + else + cursorIdx + val high = #textLength buffer - 1 + in + finishDeleteByDfa (app, cursorIdx, high, buffer, time) + end + else + finishDeleteByDfa (app, cursorIdx, otherIdx, buffer, time) + else if otherIdx < cursorIdx then + finishDeleteByDfa (app, otherIdx, cursorIdx, buffer, time) + else + NormalFinish.clearMode app + end + + fun deleteCharsLeft (app: app_type, count, time) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val startOfLine = Cursor.vi0 (buffer, cursorIdx) + val low = Cursor.viH (buffer, cursorIdx, count) + val low = if low < startOfLine then startOfLine else low + + val length = cursorIdx - low + in + deleteAndFinish (app, low, length, buffer, time) + end + + fun deleteToEndOfPrevWord (app: app_type, count, time) = + if #cursorIdx app = 0 then + NormalFinish.clearMode app + else + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = Cursor.endOfPrevWord (buffer, cursorIdx, count) + + val length = (cursorIdx + 1) - low + + val buffer = LineGap.goToIdx (cursorIdx + 1, buffer) + val initialMsg = Fn.initMsgs (low, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (low, length, buffer, searchList, dfa) + + val buffer = LineGap.goToIdx (low, buffer) + val newCursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, low) then Int.max (low - 1, 0) + else low + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + + fun deleteToEndOfPrevWORD (app: app_type, count, time) = + if #cursorIdx app = 0 then + NormalFinish.clearMode app + else + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = Cursor.endOfPrevWORD (buffer, cursorIdx, count) + + val length = (cursorIdx + 1) - low + + val buffer = LineGap.goToIdx (cursorIdx + 1, buffer) + val initialMsg = Fn.initMsgs (low, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (low, length, buffer, searchList, dfa) + + val buffer = LineGap.goToIdx (low, buffer) + val newCursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, low) then Int.max (low - 1, 0) + else low + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + + fun deleteToEndOfLine (app: app_type, time) = + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + in + if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then + (* if we are on \n, we don't want to delete or do anything + * so reset the mode *) + NormalFinish.clearMode app + else + let + val lineStart = Cursor.vi0 (buffer, cursorIdx) + val high = Cursor.viDlr (buffer, cursorIdx, 1) + 1 + val length = high - cursorIdx + + val buffer = LineGap.goToIdx (high, buffer) + val initialMsg = Fn.initMsgs (cursorIdx, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (cursorIdx, length, buffer, searchList, dfa) + + (* calculate new cursorIdx. + * Because we deleted the cursor that this line is on, + * we need to set the cursorIdx to the place + * that is considered to be the "end of line" + * after having performed the deletion. *) + val buffer = LineGap.goToIdx (lineStart, buffer) + val cursorIdx = Cursor.viDlr (buffer, lineStart, 1) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, cursorIdx) then cursorIdx - 1 + else cursorIdx + in + finishAfterDeletingBuffer + (app, cursorIdx, buffer, searchList, time, initialMsg) + end + end + + fun deleteLine (app: app_type, count, time) = + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val startIdx = Cursor.vi0 (buffer, cursorIdx) + val buffer = LineGap.goToIdx (startIdx, buffer) + + val startLine = + if Cursor.isCursorAtStartOfLine (buffer, startIdx) then + LineGap.idxToLineNumber (startIdx, buffer) + else + LineGap.idxToLineNumber (startIdx + 1, buffer) + val endLine = startLine + count + + val buffer = LineGap.goToLine (endLine, buffer) + val endLineIdx = LineGap.lineNumberToIdx (endLine, buffer) + val buffer = LineGap.goToIdx (endLineIdx, buffer) + val endLineIdx = + if Cursor.isCursorAtStartOfLine (buffer, endLineIdx) then endLineIdx + 1 + else endLineIdx + + val length = endLineIdx - startIdx + val initialMsg = Fn.initMsgs (startIdx, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (startIdx, length, buffer, searchList, dfa) + in + (* just need to reposition the cursor *) + moveCursorAfterDeletingLines + (app, buffer, time, initialMsg, startIdx, searchList) + end + + fun deleteLineDown (app: app_type, count, time) = + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val startIdx = Cursor.vi0 (buffer, cursorIdx) + val buffer = LineGap.goToIdx (startIdx, buffer) + + val startLine = + if Cursor.isCursorAtStartOfLine (buffer, startIdx) then + LineGap.idxToLineNumber (startIdx, buffer) + else + LineGap.idxToLineNumber (startIdx + 1, buffer) + val endLine = startLine + count + 1 + + val buffer = LineGap.goToLine (endLine, buffer) + val endLineIdx = LineGap.lineNumberToIdx (endLine, buffer) + val buffer = LineGap.goToIdx (endLineIdx - 1, buffer) + + (* get "real" endLine by not considering newline after non-newline *) + val endLine = + if Cursor.isOnNewlineAfterChr (buffer, endLineIdx - 1) then + LineGap.idxToLineNumber (endLineIdx - 1, buffer) + else + LineGap.idxToLineNumber (endLineIdx, buffer) + in + if endLineIdx = #textLength buffer andalso endLine = startLine then + (* cursor is already on last line so not deleting *) + NormalModeWith.bufferMsgsAndMode (app, buffer, [], NORMAL_MODE "") + else + let + val buffer = LineGap.goToIdx (endLineIdx, buffer) + + (* endLineIdx is on a newline because it was retrieved + * by calling the `LineGap.lineNumberToIdx` function, + * which always returns the idx of a line break. + * Since that is the case, we want to delete that newline too, + * and we increment by 1 to do so. + * However, we don't want to delete the last newline in the file + * so we don't increment in that case. + * Edge case: if the startIdx also begins after a newline + * then it is okay for us to delete the newline at the end of the file + * because there will already be a newline at the end of the file + * after the deletion. *) + val buffer = LineGap.goToIdx (startIdx, buffer) + val startsAfterNewline = + startIdx > 0 andalso Cursor.isPrevChrStartOfLine (buffer, startIdx) + + val endLineIdx = + if endLineIdx = #textLength buffer - 1 then + if startsAfterNewline then endLineIdx + 1 else endLineIdx + else + endLineIdx + 1 + + val length = endLineIdx - startIdx + + (* perform the actual deletion *) + val buffer = LineGap.goToIdx (endLineIdx, buffer) + val initialMsg = Fn.initMsgs (startIdx, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (startIdx, length, buffer, searchList, dfa) + in + moveCursorAfterDeletingLines + (app, buffer, time, initialMsg, startIdx, searchList) + end + end + + fun finishDeleteLineUp (app, buffer, lineIdx, length, endOfLine, time) = + if endOfLine >= #textLength buffer - 2 then + (* deleting from last line *) + let + val {searchList, dfa, ...} = app + + (* go to first column of previous line *) + val buffer = LineGap.goToIdx (endOfLine, buffer) + val initialMsg = Fn.initMsgs (lineIdx, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (lineIdx, length, buffer, searchList, dfa) + + val (buffer, searchList) = + if #textLength buffer = 0 then + let val buffer = LineGap.fromString "\n" + in SearchList.build (buffer, dfa) + end + else + (buffer, searchList) + + (* since we deleted from the last line, + * we want to place the cursor at the first column + * of the now-last line in the buffer. *) + val newCursorIdx = Int.max (#textLength buffer - 1, 0) + val buffer = LineGap.goToIdx (newCursorIdx, buffer) + val newCursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, newCursorIdx) then + newCursorIdx - 1 + else + newCursorIdx + val buffer = LineGap.goToIdx (newCursorIdx, buffer) + val newCursorIdx = Cursor.vi0 (buffer, newCursorIdx) + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + else + let + val {searchList, dfa, ...} = app + + (* make sure the cursorIdx will be at the first column + * of current line, after deleting from buffer. *) + val buffer = LineGap.goToIdx (lineIdx, buffer) + val newCursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, lineIdx) then lineIdx + 1 + else lineIdx + + val buffer = LineGap.goToIdx (endOfLine, buffer) + val initialMsg = Fn.initMsgs (lineIdx, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (lineIdx, length, buffer, searchList, dfa) + + val (buffer, searchList) = + if #textLength buffer = 0 then + let val buffer = LineGap.fromString "\n" + in SearchList.build (buffer, dfa) + end + else + (buffer, searchList) + in + finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, initialMsg) + end + + fun deleteLineUp (app: app_type, count, time) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val cursorLineNumber = + if Cursor.isNextChrEndOfLine (buffer, cursorIdx) then + LineGap.idxToLineNumber (cursorIdx + 1, buffer) + else + LineGap.idxToLineNumber (cursorIdx, buffer) + val newCursorLineNumber = Int.max (cursorLineNumber - count, 0) + in + if cursorLineNumber = 0 then + NormalFinish.clearMode app + else if newCursorLineNumber = 0 then + (* deleting from current line to start of file *) + let + val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1) + + (* edge case: if cursor is on a newline (if endOfLine = cursorIdx) + * then we only want to delete 1 character at this line, + * which is the newline the cursor is at. + * Otherwise, we want to delete 2 chars by default. *) + val buffer = LineGap.goToIdx (endOfLine, buffer) + val endOfLine = + if Cursor.isCursorAtStartOfLine (buffer, endOfLine) then + endOfLine + 1 + else + endOfLine + 2 + in + finishDeleteLineUp (app, buffer, 0, endOfLine, endOfLine, time) + end + else + let + val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1) + val buffer = LineGap.goToIdx (endOfLine, buffer) + val endsOnNewline = Cursor.isCursorAtStartOfLine (buffer, endOfLine) + + (* edge case: if cursor is on a newline, + * then we don't want to delete the newline + * as we are already deleting the newline + * at the start of this range *) + val endOfLine = if endsOnNewline then endOfLine else endOfLine + 1 + + val newCursorLineNumber = + if endsOnNewline andalso endOfLine = #textLength buffer - 1 then + newCursorLineNumber - 1 + else + newCursorLineNumber + val buffer = LineGap.goToLine (newCursorLineNumber, buffer) + + val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer) + val length = endOfLine - lineIdx + in + finishDeleteLineUp (app, buffer, lineIdx, length, endOfLine, time) + end + end + + fun deleteToFirstNonSpaceChr (app: app_type, time) = + let + val {buffer, cursorIdx, ...} = app + + (* move LineGap and buffer to start of line *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val otherIdx = Cursor.vi0 (buffer, cursorIdx) + + (* move cursorIdx to first character on line *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val otherIdx = Cursor.firstNonSpaceChr (buffer, otherIdx) + + val low = Int.min (cursorIdx, otherIdx) + val high = Int.max (cursorIdx, otherIdx) + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + + fun finishDeleteToChr (app, buffer, cursorIdx, otherIdx, time) = + let + val low = Int.min (cursorIdx, otherIdx) + val high = Int.max (cursorIdx, otherIdx) + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + + fun helpDeleteToChr + (app: app_type, buffer, cursorIdx, otherIdx, count, fMove, fInc, chr, time) = + if count = 0 then + finishDeleteToChr (app, buffer, cursorIdx, otherIdx, time) + else + let + val buffer = LineGap.goToIdx (otherIdx, buffer) + val newOtherIdx = fMove (buffer, otherIdx, chr) + in + if otherIdx = newOtherIdx then + finishDeleteToChr (app, buffer, cursorIdx, otherIdx, time) + else + helpDeleteToChr + ( app + , buffer + , cursorIdx + , fInc (newOtherIdx, 1) + , count - 1 + , fMove + , fInc + , chr + , time + ) + end + + fun deleteToChr (app: app_type, count, fMove, fInc, chr, time) = + helpDeleteToChr + ( app + , #buffer app + , #cursorIdx app + , #cursorIdx app + , count + , fMove + , fInc + , chr + , time + ) + + fun deleteToNextChr (app: app_type, count, chr, time) = + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val length = newCursorIdx - cursorIdx + 1 + val buffer = LineGap.goToIdx (newCursorIdx, buffer) + val initialMsg = Fn.initMsgs (cursorIdx, length, buffer) + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (cursorIdx, length, buffer, searchList, dfa) + + val (buffer, searchList) = + if #textLength buffer = 0 then + let val buffer = LineGap.fromString "\n" + in SearchList.build (buffer, dfa) + end + else + (buffer, searchList) + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = + if Cursor.isOnNewlineAfterChr (buffer, cursorIdx) then cursorIdx - 1 + else cursorIdx + in + finishAfterDeletingBuffer + (app, cursorIdx, buffer, searchList, time, initialMsg) + end + end + + fun deleteTillNextChr (app: app_type, count, chr, time) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let val length = newCursorIdx - cursorIdx + in deleteAndFinish (app, cursorIdx, length, buffer, time) + end + end + + fun deleteToPrevChr (app: app_type, count, chr, time) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let val length = cursorIdx - newCursorIdx + in deleteAndFinish (app, newCursorIdx, length, buffer, time) + end + end + + fun deleteTillPrevChr (app: app_type, count, chr, time) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val low = newCursorIdx + 1 + val length = cursorIdx - newCursorIdx - 1 + in + deleteAndFinish (app, low, length, buffer, time) + end + end + + fun deleteToStart (app: app_type, time) : AppType.app_type = + let + val {cursorIdx, buffer, windowWidth, windowHeight, dfa, searchList, ...} = + app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Cursor.viDlrForDelete (buffer, cursorIdx, 1) + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val initialMsg = Fn.initMsgs (0, cursorIdx, buffer) + + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (0, cursorIdx, buffer, searchList, dfa) + + val (buffer, searchList) = + if #textLength buffer = 0 then + let val buffer = LineGap.fromString "\n" + in SearchList.build (buffer, dfa) + end + else + (buffer, searchList) + + val buffer = LineGap.goToIdx (cursorIdx - 1111, buffer) + val (buffer, searchList) = SearchList.build (buffer, dfa) + + val cursorIdx = 0 + val startLine = 0 + val visualScrollColumn = 0 + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DRAW_TEXT drawMsg + + val msgs = DRAW drawMsg :: initialMsg + val mode = NORMAL_MODE "" + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , cursorIdx + , mode + , startLine + , searchList + , msgs + , time + , visualScrollColumn + ) + end + + fun deleteToEnd (app: app_type, time) = + let + val {buffer, cursorIdx, searchList, dfa, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val startOfLineIdx = Cursor.vi0 (buffer, cursorIdx) + val length = #textLength buffer - startOfLineIdx + + val buffer = LineGap.goToIdx (#textLength buffer, buffer) + val initialMsg = Fn.initMsgs (startOfLineIdx, length, buffer) + + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (startOfLineIdx, length, buffer, searchList, dfa) + + val (buffer, searchList) = + if #textLength buffer = 0 then + let val buffer = LineGap.fromString "\n" + in SearchList.build (buffer, dfa) + end + else + (buffer, searchList) + + val newLineEndIdx = Int.max (startOfLineIdx - 1, 0) + val buffer = LineGap.goToIdx (newLineEndIdx, buffer) + val newLineEndIdx = + if Cursor.isOnNewlineAfterChr (buffer, newLineEndIdx) then + Int.max (newLineEndIdx - 1, 0) + else + newLineEndIdx + + val buffer = LineGap.goToIdx (newLineEndIdx, buffer) + val newLineStartIdx = Cursor.vi0 (buffer, newLineEndIdx) + in + finishAfterDeletingBuffer + (app, newLineStartIdx, buffer, searchList, time, initialMsg) + end + + fun helpDeleteToMatch (app: app_type, low, high, time) = + let + val {buffer, dfa, searchList, ...} = app + val buffer = LineGap.goToIdx (high, buffer) + val length = high - low + val initialMsg = Fn.initMsgs (low, length, buffer) + + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (low, length, buffer, searchList, dfa) + + val buffer = LineGap.goToIdx (low, buffer) + in + NormalFinish.buildTextAndClear + (app, buffer, low, searchList, initialMsg, time) + end + + fun deleteToNextMatch (app: app_type, count, time) = + let + val {cursorIdx, searchList, ...} = app + val newCursorIdx = + PersistentVector.nextMatch (cursorIdx, searchList, count) + in + if newCursorIdx = ~1 orelse newCursorIdx <= cursorIdx then + NormalFinish.clearMode app + else + helpDeleteToMatch (app, cursorIdx, newCursorIdx, time) + end + + fun deleteToPrevMatch (app: app_type, count, time) = + let + val {cursorIdx, searchList, ...} = app + val newCursorIdx = + PersistentVector.prevMatch (cursorIdx, searchList, count) + in + if newCursorIdx = ~1 orelse newCursorIdx >= cursorIdx then + NormalFinish.clearMode app + else + helpDeleteToMatch (app, newCursorIdx, cursorIdx, time) + end + + fun deleteInsideWord (app: app_type, time) = + let + val {buffer, cursorIdx, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val chr = LineGap.sub (cursorIdx, buffer) + in + if chr = #"\n" then + NormalFinish.clearMode app + else if Char.isAlphaNum chr orelse chr = #"_" then + let + val low = Cursor.firstContiguousAlpha (buffer, cursorIdx) + val high = Cursor.lastContiguousAlpha (buffer, cursorIdx) + 1 + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + else if Char.isSpace chr then + let + val low = Cursor.firstContiguousSpace (buffer, cursorIdx) + val high = Cursor.lastContiguousSpace (buffer, cursorIdx) + 1 + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + else + (* char is punct *) + let + val low = Cursor.firstContiguousPunct (buffer, cursorIdx) + val high = Cursor.lastContiguousPunct (buffer, cursorIdx) + 1 + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + end + + fun deleteInsideWORD (app: app_type, time) = + let + val {buffer, cursorIdx, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val chr = LineGap.sub (cursorIdx, buffer) + in + if chr = #"\n" then + NormalFinish.clearMode app + else if Char.isSpace chr then + let + val low = Cursor.firstContiguousSpace (buffer, cursorIdx) + val high = Cursor.lastContiguousSpace (buffer, cursorIdx) + 1 + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + else + let + val low = Cursor.firstContiguousNonSpace (buffer, cursorIdx) + val high = Cursor.lastContiguousNonSpace (buffer, cursorIdx) + 1 + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + end + + fun deleteAroundWord (app: app_type, time) = + let + val {buffer, cursorIdx, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val low = Cursor.aroundWordPrev (buffer, cursorIdx) + val high = Cursor.aroundWordNext (buffer, cursorIdx) + 1 + + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + + fun deleteAroundWORD (app: app_type, time) = + let + val {buffer, cursorIdx, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = Cursor.aroundWORDPrev (buffer, cursorIdx) + val high = Cursor.aroundWORDNext (buffer, cursorIdx) + 1 + val length = high - low + in + deleteAndFinish (app, low, length, buffer, time) + end + + fun finishDeletingInsidePair (app, buffer, cursorIdx, otherIdx, dfa, time) = + let + val low = Int.min (cursorIdx, otherIdx) + val high = Int.max (cursorIdx, otherIdx) + in + if high = low + 1 then + NormalFinish.clearMode app + else + let + val deleteLow = low + 1 + val length = high - deleteLow + + val buffer = LineGap.goToIdx (high, buffer) + val initialMsg = Fn.initMsgs (deleteLow, length, buffer) + + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (deleteLow, length, buffer, #searchList app, dfa) + + val buffer = LineGap.goToIdx (low, buffer) + in + NormalFinish.buildTextAndClear + (app, buffer, low, searchList, initialMsg, time) + end + end + + fun deleteInsidePair (app: app_type, openChr, closeChr, time) = + let + val {buffer, cursorIdx, dfa, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorChr = LineGap.sub (cursorIdx, buffer) + in + if cursorChr = openChr orelse cursorChr = closeChr then + (* cursor is at pair, so match and delete *) + let + val otherIdx = Cursor.matchPair (buffer, cursorIdx) + in + if otherIdx = ~1 then + NormalFinish.clearMode app + else + finishDeletingInsidePair + (app, buffer, cursorIdx, otherIdx, dfa, time) + end + else + (* check to see if we are inside pair *) + let + val prevIdx = + Cursor.toOpenChrPrev + (buffer, cursorIdx, {openChr = openChr, closeChr = closeChr}) + in + if prevIdx = ~1 then + (* no openChr before cursor, so check after cursor *) + let + val nextIdx = + Cursor.toNextChr + (buffer, cursorIdx, {findChr = openChr, count = 1}) + in + if nextIdx = ~1 then + NormalFinish.clearMode app + else + let + val buffer = LineGap.goToIdx (nextIdx, buffer) + val matchIdx = Cursor.matchPair (buffer, nextIdx) + in + if matchIdx = ~1 then + NormalFinish.clearMode app + else + finishDeletingInsidePair + (app, buffer, nextIdx, matchIdx, dfa, time) + end + end + else + (* there is an openChr before cursor, so match it, + * and if there is a match, then delete *) + let + val buffer = LineGap.goToIdx (prevIdx, buffer) + val otherIdx = Cursor.matchPair (buffer, prevIdx) + in + if otherIdx = ~1 then + NormalFinish.clearMode app + else + finishDeletingInsidePair + (app, buffer, prevIdx, otherIdx, dfa, time) + end + end + end + + fun finishDeleteAroundPair (app, buffer, chr1Idx, chr2Idx, time) = + let + val {searchList, dfa, ...} = app + + val low = Int.min (chr1Idx, chr2Idx) + val high = Int.max (chr1Idx, chr2Idx) + 1 + val length = high - low + + val buffer = LineGap.goToIdx (high, buffer) + val initialMsg = Fn.initMsgs (low, length, buffer) + + val (buffer, searchList) = SearchList.deleteBufferAndSearchList + (low, length, buffer, searchList, dfa) + + val buffer = LineGap.goToIdx (low, buffer) + val low = + if Cursor.isOnNewlineAfterChr (buffer, low) then low - 1 else low + val buffer = LineGap.goToIdx (low, buffer) + in + finishAfterDeletingBuffer (app, low, buffer, searchList, time, initialMsg) + end + + fun deleteAroundPair (app: app_type, openChr, closeChr, time) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorChr = LineGap.sub (cursorIdx, buffer) + in + if cursorChr = closeChr orelse cursorChr = openChr then + let val pairIdx = Cursor.matchPair (buffer, cursorIdx) + in finishDeleteAroundPair (app, buffer, cursorIdx, pairIdx, time) + end + else + let + val prevIdx = + Cursor.toOpenChrPrev + (buffer, cursorIdx, {openChr = openChr, closeChr = closeChr}) + in + if prevIdx = ~1 then + (* we are not in a pair, so try to find openChr after cursor *) + let + val nextIdx = + Cursor.toNextChr + (buffer, cursorIdx, {findChr = openChr, count = 1}) + in + if nextIdx = ~1 then + NormalFinish.clearMode app + else + let + val buffer = LineGap.goToIdx (nextIdx, buffer) + val matchIdx = Cursor.matchPair (buffer, nextIdx) + in + if matchIdx = ~1 then + NormalFinish.clearMode app + else + finishDeleteAroundPair + (app, buffer, nextIdx, matchIdx, time) + end + end + else + (* we are on a pair-character, so check if pair + * is balanced and delete if it is *) + let + val buffer = LineGap.goToIdx (prevIdx, buffer) + val matchIdx = Cursor.matchPair (buffer, prevIdx) + in + if matchIdx = ~1 then NormalFinish.clearMode app + else finishDeleteAroundPair (app, buffer, prevIdx, matchIdx, time) + end + end + end + + fun deletePair (app: app_type, time) = + let + val {cursorIdx, buffer, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val otherIdx = Cursor.nextPairChr (buffer, cursorIdx) + in + if otherIdx = ~1 then + NormalFinish.clearMode app + else + let + val buffer = LineGap.goToIdx (otherIdx, buffer) + val otherIdx = Cursor.matchPair (buffer, otherIdx) + in + if otherIdx = ~1 then NormalFinish.clearMode app + else finishDeleteAroundPair (app, buffer, cursorIdx, otherIdx, time) + end + end +end diff --git a/shf/fcore/normal-mode/normal-delete.sml b/shf/fcore/normal-mode/normal-delete.sml new file mode 100644 index 0000000..4582233 --- /dev/null +++ b/shf/fcore/normal-mode/normal-delete.sml @@ -0,0 +1,5 @@ +structure NormalDelete = + MakeNormalDelete + (struct + fun initMsgs _ = [] + end) diff --git a/shf/fcore/normal-mode/normal-finish.sml b/shf/fcore/normal-mode/normal-finish.sml new file mode 100644 index 0000000..c098192 --- /dev/null +++ b/shf/fcore/normal-mode/normal-finish.sml @@ -0,0 +1,155 @@ +structure NormalFinish = +struct + open AppType + + open MailboxType + open DrawMsg + open InputMsg + + fun clearMode app = + NormalModeWith.mode (app, NORMAL_MODE "", []) + + fun buildTextAndClear + (app: app_type, buffer, cursorIdx, searchList, msgs, bufferModifyTime) = + let + val + { windowWidth + , windowHeight + , visualScrollColumn = prevScrollColumn + , startLine = prevLineNumber + , ... + } = app + + (* calculate new scroll column and start line, if there are any changes *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val visualScrollColumn = + TextScroll.getScrollColumn + (buffer, cursorIdx, windowWidth, prevScrollColumn) + + val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer) + val startLine = + TextScroll.getStartLine + (prevLineNumber, cursorLine, windowHeight, #lineLength buffer) + + (* move buffer to new startLine as required by TextBuilder.build *) + val buffer = LineGap.goToLine (startLine, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DRAW_TEXT drawMsg + val msgs = DRAW drawMsg :: msgs + + val mode = NORMAL_MODE "" + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , cursorIdx + , mode + , startLine + , searchList + , msgs + , bufferModifyTime + , visualScrollColumn + ) + end + + fun resizeText (app: app_type, newWidth, newHeight) = + let + val + { buffer + , windowWidth + , windowHeight + , startLine + , cursorIdx + , searchList + , bufferModifyTime + , visualScrollColumn = prevScrollColumn + , ... + } = app + + val newBuffer = LineGap.goToIdx (cursorIdx, buffer) + val visualScrollColumn = + TextScroll.getScrollColumn + (newBuffer, cursorIdx, newWidth, prevScrollColumn) + val newBuffer = LineGap.goToLine (startLine, newBuffer) + val lineIdx = TextBuilderUtils.getLineAbsIdxFromBuffer (startLine, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , newWidth + , newHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DRAW_TEXT drawMsg + val msgs = [DRAW drawMsg] + in + NormalModeWith.bufferAndSize + ( app + , newBuffer + , newWidth + , newHeight + , searchList + , msgs + , bufferModifyTime + , visualScrollColumn + ) + end + + fun centreToCursor (app: app_type) = + let + val + { buffer + , windowWidth + , windowHeight + , startLine = prevLineNumber + , cursorIdx + , searchList + , bufferModifyTime + , visualScrollColumn + , ... + } = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer) + val startLine = TextScroll.getLineCentre (cursorLine, windowHeight) + + val buffer = LineGap.goToLine (startLine, buffer) + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DRAW_TEXT drawMsg + val drawMsg = [DRAW drawMsg] + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , cursorIdx + , NORMAL_MODE "" + , startLine + , searchList + , drawMsg + , bufferModifyTime + , #visualScrollColumn app + ) + end +end diff --git a/shf/fcore/normal-mode/normal-mode-with.sml b/shf/fcore/normal-mode/normal-mode-with.sml new file mode 100644 index 0000000..be7a178 --- /dev/null +++ b/shf/fcore/normal-mode/normal-mode-with.sml @@ -0,0 +1,203 @@ +structure NormalModeWith = +struct + open AppType + + fun bufferMsgsAndMode (app: app_type, newBuffer, newMsgs, newMode) = + let + val + { mode = _ + , buffer = _ + , msgs = _ + , bufferModifyTime + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + , startLine + , cursorIdx + , dfa + } = app + in + { mode = newMode + , buffer = newBuffer + , msgs = newMsgs + , bufferModifyTime = bufferModifyTime + , windowWidth = windowWidth + , windowHeight = windowHeight + , searchList = searchList + , visualScrollColumn = visualScrollColumn + , startLine = startLine + , cursorIdx = cursorIdx + , dfa = dfa + } + end + + fun bufferAndSize + ( app: app_type + , newBuffer + , newWidth + , newHeight + , newSearchList + , newMsgs + , newBufferModifyTime + , newVisualScrollColumn + ) = + let + val + { mode + , buffer = _ + , bufferModifyTime = _ + , windowWidth = _ + , windowHeight = _ + , searchList = _ + , visualScrollColumn = _ + , msgs = _ + , startLine + , cursorIdx + , dfa + } = app + in + { mode = mode + , buffer = newBuffer + , bufferModifyTime = newBufferModifyTime + , windowWidth = newWidth + , windowHeight = newHeight + , searchList = newSearchList + , visualScrollColumn = newVisualScrollColumn + , msgs = newMsgs + , startLine = startLine + , cursorIdx = cursorIdx + , dfa = dfa + } + end + + fun bufferAndCursorIdx + ( app: app_type + , newBuffer + , newCursorIdx + , newMode + , newStartLine + , newSearchList + , newMsgs + , newBufferModifyTime + , newVisualScrollColumn + ) = + let + val + { mode = _ + , buffer = _ + , bufferModifyTime = _ + , cursorIdx = _ + , startLine = _ + , searchList = _ + , visualScrollColumn = _ + , msgs = _ + , windowWidth + , windowHeight + , dfa + } = app + in + { mode = newMode + , buffer = newBuffer + , bufferModifyTime = newBufferModifyTime + , cursorIdx = newCursorIdx + , startLine = newStartLine + , searchList = newSearchList + , visualScrollColumn = newVisualScrollColumn + , msgs = newMsgs + , windowWidth = windowWidth + , windowHeight = windowHeight + , dfa = dfa + } + end + + fun mode (app: app_type, newMode, newMsgs) = + let + val + { mode = _ + , msgs = _ + , buffer + , bufferModifyTime + , searchList + , cursorIdx + , windowWidth + , windowHeight + , startLine + , visualScrollColumn + , dfa + } = app + in + { mode = newMode + , msgs = newMsgs + , buffer = buffer + , bufferModifyTime = bufferModifyTime + , searchList = searchList + , cursorIdx = cursorIdx + , windowWidth = windowWidth + , windowHeight = windowHeight + , startLine = startLine + , visualScrollColumn = visualScrollColumn + , dfa = dfa + } + end + + fun modeAndBuffer (app: app_type, newBuffer, newMode, newMsgs) = + let + val + { mode = _ + , msgs = _ + , buffer = _ + , bufferModifyTime + , searchList + , cursorIdx + , windowWidth + , windowHeight + , startLine + , visualScrollColumn + , dfa + } = app + in + { mode = newMode + , msgs = newMsgs + , buffer = newBuffer + , bufferModifyTime = bufferModifyTime + , searchList = searchList + , cursorIdx = cursorIdx + , windowWidth = windowWidth + , windowHeight = windowHeight + , startLine = startLine + , visualScrollColumn = visualScrollColumn + , dfa = dfa + } + end + + fun searchList (app: app_type, newSearchList, newBuffer, newBufferModifyTime) = + let + val + { searchList = _ + , buffer = _ + , bufferModifyTime + , msgs + , mode + , cursorIdx + , windowWidth + , windowHeight + , startLine + , visualScrollColumn + , dfa + } = app + in + { searchList = newSearchList + , buffer = newBuffer + , bufferModifyTime = newBufferModifyTime + , msgs = msgs + , mode = mode + , cursorIdx = cursorIdx + , windowWidth = windowWidth + , windowHeight = windowHeight + , startLine = startLine + , visualScrollColumn = visualScrollColumn + , dfa = dfa + } + end +end diff --git a/shf/fcore/normal-mode/normal-mode.sml b/shf/fcore/normal-mode/normal-mode.sml new file mode 100644 index 0000000..befd4f1 --- /dev/null +++ b/shf/fcore/normal-mode/normal-mode.sml @@ -0,0 +1,693 @@ +structure NormalMode = +struct + (* parsing functions, deciding what to do while we are in normal mode *) + + open AppType + open InputMsg + + fun switchToNormalSearchMode (app: app_type, caseSensitive) = + NormalSearchFinish.onSearchChanged + (app, "", PersistentVector.empty, 0, 0, caseSensitive, #buffer app) + + fun getNumLength (pos, str) = + if pos = String.size str then + pos + else + let val chr = String.sub (str, pos) + in if Char.isDigit chr then getNumLength (pos + 1, str) else pos + end + + fun appendChr (app: app_type, chr, str) = + let + val str = str ^ Char.toString chr + val mode = NORMAL_MODE str + in + NormalModeWith.mode (app, mode, []) + end + + local + fun loop (app: app_type, cursorIdx, buffer, searchList, count, time) = + if count = 0 then + NormalDelete.finishAfterDeletingBuffer + (app, #cursorIdx app, buffer, searchList, time, []) + else + let + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val lineStart = Cursor.vi0 (buffer, cursorIdx) + + val (buffer, searchList) = SearchList.insert + (lineStart, " ", buffer, searchList, #dfa app) + + val buffer = LineGap.goToIdx (lineStart, buffer) + val lineEnd = Cursor.viDlr (buffer, lineStart, 1) + val buffer = LineGap.goToIdx (lineEnd, buffer) + val nextLine = Cursor.viL (buffer, lineEnd, 1) + + val count = if lineEnd = nextLine then 0 else count - 1 + in + loop (app, nextLine, buffer, searchList, count, time) + end + in + fun indnetLine (app: app_type, count, time) = + let val {buffer, searchList, cursorIdx, ...} = app + in loop (app, cursorIdx, buffer, searchList, count, time) + end + end + + local + fun loop (cursorIdx, buffer, searchList, dfa, count) = + if count = 0 then + (buffer, searchList) + else + let + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val lineStart = Cursor.vi0 (buffer, cursorIdx) + val firstNonSpaceChr = Cursor.firstNonSpaceChr (buffer, lineStart) + + (* delete from buffer *) + val difference = firstNonSpaceChr - lineStart + val deleteLength = Int.min (difference, 2) + val (buffer, searchList) = + if difference = 0 then + (* can't dedent as there is no leading space *) + (buffer, searchList) + else + SearchList.deleteBufferAndSearchList + (lineStart, deleteLength, buffer, searchList, dfa) + + (* get next line to dedent *) + val buffer = LineGap.goToIdx (lineStart, buffer) + val lineEnd = Cursor.viDlr (buffer, lineStart, 1) + val buffer = LineGap.goToIdx (lineEnd, buffer) + val nextLine = Cursor.viL (buffer, lineEnd, 1) + + val count = if lineEnd = nextLine then 0 else count - 1 + in + loop (nextLine, buffer, searchList, dfa, count) + end + in + fun dedentLine (app: app_type, count, time) = + let + open MailboxType + + val {cursorIdx, buffer, searchList, dfa, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val lineStart = Cursor.vi0 (buffer, cursorIdx) + val firstNonSpaceChr = Cursor.firstNonSpaceChr (buffer, lineStart) + + (* calculate length to delete *) + val difference = firstNonSpaceChr - lineStart + val deleteLength = Int.min (difference, 2) + + (* delete once *) + val (buffer, searchList) = + if deleteLength = 0 then + (buffer, searchList) + else + SearchList.deleteBufferAndSearchList + (lineStart, deleteLength, buffer, searchList, dfa) + + (* Calculate nextLine and newCursorIdx. + * The cursorIdx might be past the current line after we dedent. + * If it is, we put the cursorIdx at the last char of the line. *) + val buffer = LineGap.goToIdx (lineStart, buffer) + val lineEnd = Cursor.viDlr (buffer, lineStart, 1) + val buffer = LineGap.goToIdx (lineEnd, buffer) + val nextLine = Cursor.viL (buffer, lineEnd, 1) + val newCursorIdx = Int.min (lineEnd, cursorIdx) + + val (buffer, searchList) = + if lineEnd = nextLine then + (* at end of file, so we cannot dedent anymore *) + (buffer, searchList) + else + (* dedent remaining lines specified by count *) + loop (nextLine, buffer, searchList, dfa, count - 1) + + val buffer = LineGap.goToStart buffer + in + NormalDelete.finishAfterDeletingBuffer + (app, newCursorIdx, buffer, searchList, time, []) + end + end + + fun parseGo (count, app, chrCmd) = + case chrCmd of + #"e" => MoveToEndOfPrevWord.move (app, count) + | #"E" => MoveToEndOfPrevWORD.move (app, count) + | #"g" => NormalMove.moveToStart app + | _ => NormalFinish.clearMode app + + fun parseChr (app: app_type, count, chr, str, time) = + case chr of + #"h" => MoveViH.move (app, count) + | #"j" => NormalMove.moveCursorDown (app, count) + | #"k" => NormalMove.moveCursorUp (app, count) + | #"l" => MoveViL.move (app, count) + | #"w" => MoveToNextWord.move (app, count) + | #"W" => MoveToNextWORD.move (app, count) + | #"b" => MoveToPrevWord.move (app, count) + | #"B" => MoveToPrevWORD.move (app, count) + | #"e" => MoveToEndOfWord.move (app, count) + | #"E" => MoveToEndOfWORD.move (app, count) + | #"n" => NormalMove.moveToNextMatch (app, count) + | #"N" => NormalMove.moveToPrevMatch (app, count) + | #"z" => NormalFinish.centreToCursor app + (* can only move to start or end of line once + * so hardcode count as 1 *) + | #"0" => + (* 0 is a bit of a special case. + * If 0 is pressed without any preceding characters, + * then it should move cursor to the start of the line. + * However, if a number was pressed previously before 0 was, + * then this means user is entering a count. + * In that case, we append 0 to the string. *) + if String.size str > 0 then + let + val lastChr = String.sub (str, String.size str - 1) + in + if Char.isDigit lastChr then + let + val chr = Char.toString chr + val str = str ^ chr + val mode = NORMAL_MODE str + in + NormalModeWith.mode (app, mode, []) + end + else + MoveToStartOfLine.move (app, 1) + end + else + MoveToStartOfLine.move (app, 1) + | #"$" => MoveToEndOfLine.move (app, 1) + | #"^" => NormalMove.firstNonSpaceChr app + | #"G" => + (* if str has a size larger than 0, + * interpret as "go to line" command; + * else, interpret as a command to move to end *) + if String.size str = 0 then NormalMove.moveToEnd app + else NormalMove.moveToLine (app, count) + | #"%" => NormalMove.moveToMatchingPair app + | #"D" => NormalDelete.deleteToEndOfLine (app, time) + | #"x" => NormalDelete.removeChr (app, count, time) + | #"J" => NormalDelete.removeLineBreaks (app, count, time) + | #"/" => switchToNormalSearchMode (app, false) + | #"?" => switchToNormalSearchMode (app, true) + | #">" => indnetLine (app, count, time) + | #"<" => dedentLine (app, count, time) + (* multi-char commands which can be appended *) + | #"t" => appendChr (app, chr, str) + | #"T" => appendChr (app, chr, str) + | #"y" => appendChr (app, chr, str) + | #"d" => appendChr (app, chr, str) + | #"f" => appendChr (app, chr, str) + | #"F" => appendChr (app, chr, str) + | #"g" => appendChr (app, chr, str) + | #"c" => appendChr (app, chr, str) + | _ => + (* user may be entering a cmd with more than one chr + * such as "2dw" to delete two word + * so add current chr to mode, and save it in the app state *) + let + val str = if Char.isDigit chr then str ^ Char.toString chr else "" + val mode = NORMAL_MODE str + in + NormalModeWith.mode (app, mode, []) + end + + structure ParseDelete = + struct + fun parseDeleteInside (app, chr, time) = + case chr of + #"w" => NormalDelete.deleteInsideWord (app, time) + | #"W" => NormalDelete.deleteInsideWORD (app, time) + + | #"(" => NormalDelete.deleteInsidePair (app, #"(", #")", time) + | #")" => NormalDelete.deleteInsidePair (app, #"(", #")", time) + + | #"[" => NormalDelete.deleteInsidePair (app, #"[", #"]", time) + | #"]" => NormalDelete.deleteInsidePair (app, #"[", #"]", time) + + | #"{" => NormalDelete.deleteInsidePair (app, #"{", #"}", time) + | #"}" => NormalDelete.deleteInsidePair (app, #"{", #"}", time) + + | #"<" => NormalDelete.deleteInsidePair (app, #"<", #">", time) + | #">" => NormalDelete.deleteInsidePair (app, #"<", #">", time) + | _ => NormalFinish.clearMode app + + fun parseDeleteAround (app, chr, time) = + case chr of + #"w" => NormalDelete.deleteAroundWord (app, time) + | #"W" => NormalDelete.deleteAroundWORD (app, time) + + + | #"(" => NormalDelete.deleteAroundPair (app, #"(", #")", time) + | #")" => NormalDelete.deleteAroundPair (app, #"(", #")", time) + + | #"[" => NormalDelete.deleteAroundPair (app, #"[", #"]", time) + | #"]" => NormalDelete.deleteAroundPair (app, #"[", #"]", time) + + | #"{" => NormalDelete.deleteAroundPair (app, #"{", #"}", time) + | #"}" => NormalDelete.deleteAroundPair (app, #"{", #"}", time) + + | #"<" => NormalDelete.deleteAroundPair (app, #"<", #">", time) + | #">" => NormalDelete.deleteAroundPair (app, #"<", #">", time) + | _ => NormalFinish.clearMode app + + fun parseDeleteTerminal (str, count, app, chrCmd, time) = + case chrCmd of + (* terminal commands: require no input after *) + #"h" => NormalDelete.deleteCharsLeft (app, count, time) + | #"l" => NormalDelete.removeChr (app, count, time) + (* vi's 'j' and 'k' commands move up or down a column + * but 'dj' or 'dk' delete whole lines + * so their implementation differs from + * other cursor motions *) + | #"j" => NormalDelete.deleteLineDown (app, count, time) + | #"k" => NormalDelete.deleteLineUp (app, count, time) + | #"w" => NormalDelete.deleteWord (app, count, time) + | #"W" => NormalDelete.deleteWORD (app, count, time) + | #"b" => NormalDelete.deleteByDfa (app, count, Cursor.prevWord, time) + | #"B" => NormalDelete.deleteByDfa (app, count, Cursor.prevWORD, time) + | #"e" => + NormalDelete.deleteByDfa (app, count, Cursor.endOfWordForDelete, time) + | #"E" => + NormalDelete.deleteByDfa (app, count, Cursor.endOfWORDForDelete, time) + | #"0" => NormalDelete.delete (app, 1, Cursor.vi0, time) + | #"$" => NormalDelete.deleteToEndOfLine (app, time) + | #"^" => NormalDelete.deleteToFirstNonSpaceChr (app, time) + | #"d" => NormalDelete.deleteLine (app, count, time) + | #"n" => NormalDelete.deleteToNextMatch (app, count, time) + | #"N" => NormalDelete.deleteToPrevMatch (app, count, time) + | #"%" => NormalDelete.deletePair (app, time) + | #"G" => NormalDelete.deleteToEnd (app, time) + (* non-terminal commands which require appending chr *) + | #"t" => appendChr (app, chrCmd, str) + | #"T" => appendChr (app, chrCmd, str) + | #"f" => appendChr (app, chrCmd, str) + | #"F" => appendChr (app, chrCmd, str) + | #"g" => appendChr (app, chrCmd, str) + | #"i" => appendChr (app, chrCmd, str) + | #"a" => appendChr (app, chrCmd, str) + (* invalid command: reset mode *) + | _ => NormalFinish.clearMode app + + fun parseDeleteGo (app, count, chrCmd, time) = + case chrCmd of + #"e" => NormalDelete.deleteToEndOfPrevWord (app, count, time) + | #"E" => NormalDelete.deleteToEndOfPrevWORD (app, count, time) + | #"g" => NormalDelete.deleteToStart (app, time) + | _ => NormalFinish.clearMode app + + fun parseDelete (strPos, str, count, app, chrCmd, time) = + if strPos = String.size str - 1 then + parseDeleteTerminal (str, count, app, chrCmd, time) + else + (* have to continue parsing string *) + case String.sub (str, strPos + 1) of + #"t" => NormalDelete.deleteTillNextChr (app, count, chrCmd, time) + | #"T" => NormalDelete.deleteTillPrevChr (app, count, chrCmd, time) + | #"f" => NormalDelete.deleteToNextChr (app, count, chrCmd, time) + | #"F" => NormalDelete.deleteToPrevChr (app, count, chrCmd, time) + | #"g" => parseDeleteGo (app, count, chrCmd, time) + | #"i" => parseDeleteInside (app, chrCmd, time) + | #"a" => parseDeleteAround (app, chrCmd, time) + | _ => NormalFinish.clearMode app + end + + structure ParseYankDelete = + struct + fun parseDeleteInside (app, chr, time) = + case chr of + #"w" => NormalYankDelete.deleteInsideWord (app, time) + | #"W" => NormalYankDelete.deleteInsideWORD (app, time) + + | #"(" => NormalYankDelete.deleteInsidePair (app, #"(", #")", time) + | #")" => NormalYankDelete.deleteInsidePair (app, #"(", #")", time) + + | #"[" => NormalYankDelete.deleteInsidePair (app, #"[", #"]", time) + | #"]" => NormalYankDelete.deleteInsidePair (app, #"[", #"]", time) + + | #"{" => NormalYankDelete.deleteInsidePair (app, #"{", #"}", time) + | #"}" => NormalYankDelete.deleteInsidePair (app, #"{", #"}", time) + + | #"<" => NormalYankDelete.deleteInsidePair (app, #"<", #">", time) + | #">" => NormalYankDelete.deleteInsidePair (app, #"<", #">", time) + | _ => NormalFinish.clearMode app + + fun parseDeleteAround (app, chr, time) = + case chr of + #"w" => NormalYankDelete.deleteAroundWord (app, time) + | #"W" => NormalYankDelete.deleteAroundWORD (app, time) + + + | #"(" => NormalYankDelete.deleteAroundPair (app, #"(", #")", time) + | #")" => NormalYankDelete.deleteAroundPair (app, #"(", #")", time) + + | #"[" => NormalYankDelete.deleteAroundPair (app, #"[", #"]", time) + | #"]" => NormalYankDelete.deleteAroundPair (app, #"[", #"]", time) + + | #"{" => NormalYankDelete.deleteAroundPair (app, #"{", #"}", time) + | #"}" => NormalYankDelete.deleteAroundPair (app, #"{", #"}", time) + + | #"<" => NormalYankDelete.deleteAroundPair (app, #"<", #">", time) + | #">" => NormalYankDelete.deleteAroundPair (app, #"<", #">", time) + | _ => NormalFinish.clearMode app + + fun parseDeleteTerminal (str, count, app, chrCmd, time) = + case chrCmd of + (* terminal commands: require no input after *) + #"h" => NormalYankDelete.deleteCharsLeft (app, count, time) + | #"l" => NormalYankDelete.removeChr (app, count, time) + (* vi's 'j' and 'k' commands move up or down a column + * but 'dj' or 'dk' delete whole lines + * so their implementation differs from + * other cursor motions *) + | #"j" => NormalYankDelete.deleteLineDown (app, count, time) + | #"k" => NormalYankDelete.deleteLineUp (app, count, time) + | #"w" => NormalYankDelete.deleteWord (app, count, time) + | #"W" => NormalYankDelete.deleteWORD (app, count, time) + | #"b" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWord, time) + | #"B" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWORD, time) + | #"e" => + NormalYankDelete.deleteByDfa + (app, count, Cursor.endOfWordForDelete, time) + | #"E" => + NormalYankDelete.deleteByDfa + (app, count, Cursor.endOfWORDForDelete, time) + | #"0" => NormalYankDelete.delete (app, 1, Cursor.vi0, time) + | #"$" => NormalYankDelete.deleteToEndOfLine (app, time) + | #"^" => NormalYankDelete.deleteToFirstNonSpaceChr (app, time) + | #"d" => NormalYankDelete.deleteLine (app, count, time) + | #"n" => NormalYankDelete.deleteToNextMatch (app, count, time) + | #"N" => NormalYankDelete.deleteToPrevMatch (app, count, time) + | #"%" => NormalYankDelete.deletePair (app, time) + | #"G" => NormalYankDelete.deleteToEnd (app, time) + (* non-terminal commands which require appending chr *) + | #"t" => appendChr (app, chrCmd, str) + | #"T" => appendChr (app, chrCmd, str) + | #"f" => appendChr (app, chrCmd, str) + | #"F" => appendChr (app, chrCmd, str) + | #"g" => appendChr (app, chrCmd, str) + | #"i" => appendChr (app, chrCmd, str) + | #"a" => appendChr (app, chrCmd, str) + (* invalid command: reset mode *) + | _ => NormalFinish.clearMode app + + fun parseDeleteGo (app, count, chrCmd, time) = + case chrCmd of + #"e" => NormalYankDelete.deleteToEndOfPrevWord (app, count, time) + | #"E" => NormalYankDelete.deleteToEndOfPrevWORD (app, count, time) + | #"g" => NormalYankDelete.deleteToStart (app, time) + | _ => NormalFinish.clearMode app + + fun parseDelete (strPos, str, count, app, chrCmd, time) = + if strPos = String.size str - 1 then + parseDeleteTerminal (str, count, app, chrCmd, time) + else + (* have to continue parsing string *) + case String.sub (str, strPos + 1) of + #"t" => NormalYankDelete.deleteTillNextChr (app, count, chrCmd, time) + | #"T" => NormalYankDelete.deleteTillPrevChr (app, count, chrCmd, time) + | #"f" => NormalYankDelete.deleteToNextChr (app, count, chrCmd, time) + | #"F" => NormalYankDelete.deleteToPrevChr (app, count, chrCmd, time) + | #"g" => parseDeleteGo (app, count, chrCmd, time) + | #"i" => parseDeleteInside (app, chrCmd, time) + | #"a" => parseDeleteAround (app, chrCmd, time) + | _ => NormalFinish.clearMode app + end + + structure ParseYank = + struct + fun yankWhenMovingBack (app: app_type, fMove, count) = + let + open DrawMsg + open MailboxType + + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = fMove (buffer, cursorIdx, count) + + val length = cursorIdx - low + val str = LineGap.substring (low, length, buffer) + + val msg = YANK str + val mode = NORMAL_MODE "" + in + NormalModeWith.modeAndBuffer (app, buffer, mode, [DRAW msg]) + end + + fun yankWhenMovingForward (app: app_type, fMove, count) = + let + open DrawMsg + open MailboxType + + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val high = fMove (buffer, cursorIdx, count) + + val buffer = LineGap.goToIdx (high, buffer) + val length = high - cursorIdx + val str = LineGap.substring (cursorIdx, length, buffer) + + val msg = YANK str + val mode = NORMAL_MODE "" + in + NormalModeWith.modeAndBuffer (app, buffer, mode, [DRAW msg]) + end + + fun parseYankTerminal (str, count, app, chrCmd, time) = + case chrCmd of + #"h" => NormalYank.yankLeft (app, count) + | #"k" => NormalYank.yankLineUp (app, count) + | #"j" => NormalYank.yankLineDown (app, count) + | #"l" => NormalYank.yankRight (app, count) + | #"y" => NormalYank.yankLine (app, count) + | #"0" => NormalYank.yankToStartOfLine app + | #"w" => NormalYank.yankWhenMovingForward (app, Cursor.nextWord, count) + | #"W" => NormalYank.yankWhenMovingForward (app, Cursor.nextWORD, count) + | #"b" => NormalYank.yankWhenMovingBack (app, Cursor.prevWord, count) + | #"B" => NormalYank.yankWhenMovingBack (app, Cursor.prevWORD, count) + | #"e" => + NormalYank.yankWhenMovingForward + (app, Cursor.endOfWordForDelete, count) + | #"E" => + NormalYank.yankWhenMovingForward + (app, Cursor.endOfWORDForDelete, count) + | #"$" => NormalYank.yankWhenMovingForward (app, Cursor.viDlr, 1) + | #"^" => NormalYank.yankToFirstNonSpaceChr app + | #"G" => NormalYank.yankToEndOfText app + | #"%" => NormalYank.yankToMatchingPair app + | #"n" => NormalYank.yankToNextMatch (app, count) + | #"N" => NormalYank.yankToPrevMatch (app, count) + | #"x" => NormalYankDelete.removeChr (app, count, time) + (* append non-terminal characters to string *) + | #"d" => + let (* 'yd' motion, like 'ydw'; meant to be 'yank then delete' *) + in appendChr (app, chrCmd, str) + end + | #"t" => appendChr (app, chrCmd, str) + | #"T" => appendChr (app, chrCmd, str) + | #"f" => appendChr (app, chrCmd, str) + | #"F" => appendChr (app, chrCmd, str) + | #"g" => appendChr (app, chrCmd, str) + | #"i" => appendChr (app, chrCmd, str) + | #"a" => appendChr (app, chrCmd, str) + | _ => NormalFinish.clearMode app + + fun parseYankGo (count, app, chrCmd) = + case chrCmd of + #"e" => + NormalYank.yankWhenMovingBackPlusOne + (app, Cursor.endOfPrevWord, count) + | #"E" => + NormalYank.yankWhenMovingBackPlusOne + (app, Cursor.endOfPrevWORD, count) + | #"g" => NormalYank.yankToStart app + | _ => NormalFinish.clearMode app + + fun parseYankInside (app, chr) = + case chr of + #"w" => NormalYank.yankInsideWord app + | #"W" => NormalYank.yankInsideWORD app + | #"(" => NormalYank.yankInsideChrOpen (app, chr) + | #"[" => NormalYank.yankInsideChrOpen (app, chr) + | #"{" => NormalYank.yankInsideChrOpen (app, chr) + | #"<" => NormalYank.yankInsideChrOpen (app, chr) + | #")" => NormalYank.yankInsideChrClose (app, chr) + | #"]" => NormalYank.yankInsideChrClose (app, chr) + | #"}" => NormalYank.yankInsideChrClose (app, chr) + | #">" => NormalYank.yankInsideChrClose (app, chr) + | _ => NormalFinish.clearMode app + + fun parseYankAround (app, chr) = + case chr of + #"(" => NormalYank.yankAroundChrOpen (app, chr) + | #"[" => NormalYank.yankAroundChrOpen (app, chr) + | #"{" => NormalYank.yankAroundChrOpen (app, chr) + | #"<" => NormalYank.yankAroundChrOpen (app, chr) + | #")" => NormalYank.yankAroundChrClose (app, chr) + | #"]" => NormalYank.yankAroundChrClose (app, chr) + | #"}" => NormalYank.yankAroundChrClose (app, chr) + | #">" => NormalYank.yankAroundChrClose (app, chr) + | _ => NormalFinish.clearMode app + + fun parseYank (strPos, str, count, app, chrCmd, time) = + if strPos = String.size str - 1 then + parseYankTerminal (str, count, app, chrCmd, time) + else + case String.sub (str, strPos + 1) of + #"t" => NormalYank.yankTillNextChr (app, count, chrCmd) + | #"T" => NormalYank.yankTillPrevChr (app, count, chrCmd) + | #"f" => NormalYank.yankToNextChr (app, count, chrCmd) + | #"F" => NormalYank.yankToPrevChr (app, count, chrCmd) + | #"g" => parseYankGo (count, app, chrCmd) + | #"i" => parseYankInside (app, chrCmd) + | #"a" => parseYankAround (app, chrCmd) + | #"d" => + ParseYankDelete.parseDelete + (strPos + 1, str, count, app, chrCmd, time) + | _ => NormalFinish.clearMode app + end + + (* useful reference as list of non-terminal commands *) + fun parseAfterCount (strPos, str, count, app, chrCmd, time) = + (* we are trying to parse multi-char but non-terminal strings here. + * For example, we don't want to parse 3w which is a terminal commmand + * to go 3 words forwards + * but we do want to parse 3d which is a non-terminal command + * which can be made terminal by adding "w" or "e" at the end. + * *) + case String.sub (str, strPos) of + #"t" => NormalMove.tillNextChr (app, count, chrCmd) + | #"T" => NormalMove.tillPrevChr (app, count, chrCmd) + | #"y" => ParseYank.parseYank (strPos, str, count, app, chrCmd, time) + | #"d" => ParseDelete.parseDelete (strPos, str, count, app, chrCmd, time) + | #"f" => NormalMove.toNextChr (app, count, chrCmd) + | #"F" => NormalMove.toPrevChr (app, count, chrCmd) + | #"g" => (* go *) parseGo (count, app, chrCmd) + | #"c" => (* change *) NormalFinish.clearMode app + | _ => + (* isn't a non-terminal cmd + * this case should never happen*) + NormalFinish.clearMode app + + fun parseNormalModeCommand (app, str, chrCmd, time) = + if String.size str = 0 then + parseChr (app, 1, chrCmd, str, time) + else if String.size str = 1 then + case Int.fromString str of + SOME count => parseChr (app, count, chrCmd, str, time) + | NONE => parseAfterCount (0, str, 1, app, chrCmd, time) + else + let + val numLength = getNumLength (0, str) + val count = String.substring (str, 0, numLength) + val count = + case Int.fromString count of + SOME x => x + | NONE => 1 + in + if numLength = String.size str then + (* reached end of str; str only contained numbers *) + parseChr (app, count, chrCmd, str, time) + else + (* continue parsing. *) + parseAfterCount (numLength, str, count, app, chrCmd, time) + end + + structure LeftArrow = + struct + fun parseLeftArrowCommand (strPos, str, count, app, time) = + case String.sub (str, strPos) of + #"y" => + if strPos + 1 = String.size str then + (* terminal command, so simple yank *) + raise Fail "left-arrow-yank unimplemnted" + else + (case String.sub (str, strPos + 1) of + #"d" => NormalYankDelete.deleteCharsLeft (app, count, time) + | _ => NormalFinish.clearMode app) + | #"d" => NormalDelete.deleteCharsLeft (app, count, time) + | _ => NormalFinish.clearMode app + + fun parse (app, str, time) = + if String.size str = 0 then + MoveViH.move (app, 1) + else if String.size str = 1 then + case Int.fromString str of + SOME count => MoveViH.move (app, count) + | NONE => parseLeftArrowCommand (0, str, 1, app, time) + else + let + val numLength = getNumLength (0, str) + val count = String.substring (str, 0, numLength) + val count = + case Int.fromString count of + SOME x => x + | NONE => 1 + in + if numLength = String.size str then + (* reached end of string; string only contained numbers *) + MoveViH.move (app, count) + else + parseLeftArrowCommand (numLength, str, count, app, time) + end + end + + structure RightArrow = + struct + fun parseRightArrowCommand (strPos, str, count, app, time) = + case String.sub (str, strPos) of + #"y" => + if strPos + 1 = String.size str then + raise Fail "right-arrow-yank unimplemnted" + else + (case String.sub (str, strPos + 1) of + #"d" => NormalYankDelete.removeChr (app, count, time) + | _ => NormalFinish.clearMode app) + | #"d" => NormalDelete.removeChr (app, count, time) + | _ => NormalFinish.clearMode app + + fun parse (app, str, time) = + if String.size str = 0 then + MoveViL.move (app, 1) + else if String.size str = 1 then + case Int.fromString str of + SOME count => MoveViL.move (app, count) + | NONE => parseRightArrowCommand (0, str, 1, app, time) + else + let + val numLength = getNumLength (0, str) + val count = String.substring (str, 0, numLength) + val count = + case Int.fromString count of + SOME x => x + | NONE => 1 + in + if numLength = String.size str then + (* reached end of string; string only contained numbers *) + MoveViH.move (app, count) + else + parseRightArrowCommand (numLength, str, count, app, time) + end + end + + fun update (app, str, msg, time) = + case msg of + CHAR_EVENT chrCmd => parseNormalModeCommand (app, str, chrCmd, time) + | KEY_ESC => NormalFinish.clearMode app + | RESIZE_EVENT (width, height) => + NormalFinish.resizeText (app, width, height) + + | ARROW_RIGHT => RightArrow.parse (app, str, time) + | ARROW_LEFT => LeftArrow.parse (app, str, time) + | ARROW_UP => NormalFinish.clearMode app + | ARROW_DOWN => NormalFinish.clearMode app + + | KEY_ENTER => NormalFinish.clearMode app + | KEY_BACKSPACE => NormalFinish.clearMode app +end diff --git a/shf/fcore/normal-mode/normal-move.sml b/shf/fcore/normal-mode/normal-move.sml new file mode 100644 index 0000000..4610c75 --- /dev/null +++ b/shf/fcore/normal-mode/normal-move.sml @@ -0,0 +1,586 @@ +structure NormalMove = +struct + open AppType + + fun moveToStart (app: app_type) : AppType.app_type = + let + val + { buffer + , windowWidth + , windowHeight + , searchList + , bufferModifyTime + , visualScrollColumn + , ... + } = app + + val cursorIdx = 0 + val startLine = 0 + val buffer = LineGap.goToStart buffer + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val drawMsg = [MailboxType.DRAW drawMsg] + + val mode = NORMAL_MODE "" + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , cursorIdx + , mode + , startLine + , searchList + , drawMsg + , bufferModifyTime + , 0 + ) + end + + fun moveToEnd (app: app_type) = + let + val + { buffer + , windowWidth + , windowHeight + , searchList + , bufferModifyTime + , visualScrollColumn = prevScrollColumn + , startLine = prevLineNumber + , ... + } = app + + val buffer = LineGap.goToEnd buffer + val {line = bufferLine, textLength, ...} = buffer + + val bufferIdx = Int.max (0, textLength - 1) + val bufferLine = bufferLine - 1 + + val buffer = LineGap.goToIdx (bufferIdx, buffer) + val bufferIdx = + if Cursor.isOnNewlineAfterChr (buffer, bufferIdx) then + Int.max (0, bufferIdx - 1) + else + bufferIdx + + val buffer = LineGap.goToIdx (bufferIdx, buffer) + val visualScrollColumn = + TextScroll.getScrollColumn + (buffer, bufferIdx, windowWidth, prevScrollColumn) + + val bufferLine = + TextScroll.getStartLine + (prevLineNumber, bufferLine, windowHeight, #lineLength buffer) + val buffer = LineGap.goToLine (bufferLine, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( bufferLine + , bufferIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val drawMsg = [MailboxType.DRAW drawMsg] + + val mode = NORMAL_MODE "" + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , bufferIdx + , mode + , bufferLine + , searchList + , drawMsg + , bufferModifyTime + , visualScrollColumn + ) + end + + fun finishMoveCursorUpDown + (app: app_type, newCursorLineNumber, buffer, column, lineIdx) = + let + val + { windowWidth + , windowHeight + , visualScrollColumn = prevScrollColumn + , startLine = prevLineNumber + , searchList + , bufferModifyTime + , ... + } = app + + val buffer = LineGap.goToIdx (lineIdx, buffer) + val endOfLineIdx = Cursor.viDlr (buffer, lineIdx, 1) + val endOfLineIdx = + if endOfLineIdx >= #textLength buffer - 1 then + Int.max (0, #textLength buffer - 1) + else + endOfLineIdx + + val cursorIdx = Int.min (endOfLineIdx, lineIdx + column) + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + (* create draw message *) + val visualScrollColumn = + TextScroll.getScrollColumn + (buffer, cursorIdx, windowWidth, prevScrollColumn) + + val startLine = + TextScroll.getStartLine + ( prevLineNumber + , newCursorLineNumber + , windowHeight + , #lineLength buffer + ) + + val buffer = LineGap.goToLine (startLine, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val drawMsg = [MailboxType.DRAW drawMsg] + + val mode = NORMAL_MODE "" + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , cursorIdx + , mode + , startLine + , searchList + , drawMsg + , bufferModifyTime + , visualScrollColumn + ) + end + + fun moveCursorUp (app: app_type, count) = + let + val {cursorIdx, buffer, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val startOfLine = Cursor.vi0 (buffer, cursorIdx) + in + if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then + let + val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx + 1, buffer) + val newCursorLineNumber = Int.max (0, cursorLineNumber - count) + val buffer = LineGap.goToLine (newCursorLineNumber, buffer) + val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer) + + val lineIdx = + if Cursor.isPrevChrStartOfLine (buffer, lineIdx) then lineIdx + else lineIdx - 1 + + val buffer = LineGap.goToIdx (lineIdx, buffer) + val lineIdx = Cursor.vi0 (buffer, lineIdx) + + val lineIdx = Int.max (0, lineIdx) + in + finishMoveCursorUpDown (app, newCursorLineNumber, buffer, 0, lineIdx) + end + else + let + val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx, buffer) + val newCursorLineNumber = Int.max (cursorLineNumber - count, 0) + + val buffer = LineGap.goToLine (newCursorLineNumber, buffer) + val lineIdx = + LineGap.lineNumberToIdx (newCursorLineNumber, buffer) + 1 + + val column = cursorIdx - startOfLine + val column = if newCursorLineNumber = 0 then column - 1 else column + in + finishMoveCursorUpDown + (app, newCursorLineNumber, buffer, column, lineIdx) + end + end + + fun moveCursorDown (app: app_type, count) = + let + val + { windowWidth + , windowHeight + , cursorIdx + , buffer + , startLine = prevLineNumber + , searchList + , bufferModifyTime + , visualScrollColumn = prevScrollColumn + , ... + } = app + + (* calculate new idx to move to *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val startOfLine = Cursor.vi0 (buffer, cursorIdx) + val column = cursorIdx - startOfLine + in + if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then + let + val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx + 1, buffer) + val newCursorLineNumber = cursorLineNumber + count + + val buffer = LineGap.goToLine (newCursorLineNumber, buffer) + val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer) + + val lineIdx = + if Cursor.isPrevChrStartOfLine (buffer, lineIdx) then lineIdx + else lineIdx - 1 + val buffer = LineGap.goToIdx (lineIdx, buffer) + val lineIdx = Cursor.vi0 (buffer, lineIdx) + + val lineIdx = + if lineIdx >= #textLength buffer - 1 then + Int.max (0, #textLength buffer - 1) + else + lineIdx + in + finishMoveCursorUpDown (app, newCursorLineNumber, buffer, 0, lineIdx) + end + else + let + val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx, buffer) + val newCursorLineNumber = cursorLineNumber + count + + val buffer = LineGap.goToLine (newCursorLineNumber, buffer) + val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer) + val buffer = LineGap.goToIdx (lineIdx, buffer) + in + if lineIdx >= #textLength buffer - 1 then + (* we reached last line *) + let + val lineIdx = Int.max (#textLength buffer - 1, 0) + val buffer = LineGap.goToIdx (lineIdx, buffer) + + val lineIdx = + if Cursor.isOnNewlineAfterChr (buffer, lineIdx) then lineIdx - 1 + else lineIdx + + val startOfLine = Cursor.vi0 (buffer, lineIdx) + in + if cursorIdx >= startOfLine then + (* we are already on last line so don't move *) + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, searchList, [], bufferModifyTime) + else + finishMoveCursorUpDown + (app, newCursorLineNumber, buffer, column, startOfLine) + end + else + let + val lineIdx = + if lineIdx >= #textLength buffer - 2 then + Int.max (0, #textLength buffer - 2) + else + lineIdx + + val buffer = LineGap.goToIdx (lineIdx, buffer) + val lineIdx = + if Cursor.isOnNewlineAfterChr (buffer, lineIdx) then lineIdx + 1 + else lineIdx + in + finishMoveCursorUpDown + (app, newCursorLineNumber, buffer, column, lineIdx) + end + end + end + + fun moveToLine (app: app_type, reqLine) = + let + val reqLine = reqLine - 1 + in + if reqLine = 0 then + moveToStart app + else + let + val + { windowWidth + , windowHeight + , buffer + , startLine = prevLineNumber + , searchList + , bufferModifyTime + , visualScrollColumn = prevScrollColumn + , ... + } = app + val buffer = LineGap.goToLine (reqLine, buffer) + + (* get idx of first chr after linebreak *) + val cursorIdx = LineGap.lineNumberToIdx (reqLine, buffer) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + (* we got the line start idx, but we want to move to the index + * after it, where the first character of the line is. + * Unless the next character is a line break, + * in which case we want to stay at the current idx. *) + val cursorIdx = + if Cursor.isNextChrEndOfLine (buffer, cursorIdx) then cursorIdx + else cursorIdx + 1 + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val visualScrollColumn = + TextScroll.getScrollColumn + (buffer, cursorIdx, windowWidth, prevScrollColumn) + + val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer) + val startLine = + TextScroll.getStartLine + (prevLineNumber, cursorLine, windowHeight, #lineLength buffer) + + val buffer = LineGap.goToLine (startLine, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val drawMsg = [MailboxType.DRAW drawMsg] + + val mode = NORMAL_MODE "" + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , cursorIdx + , mode + , startLine + , searchList + , drawMsg + , bufferModifyTime + , visualScrollColumn + ) + end + end + + fun moveToMatchingPair (app: app_type) = + let + val + { buffer + , cursorIdx + , windowWidth + , windowHeight + , startLine = prevLineNumber + , searchList + , bufferModifyTime + , visualScrollColumn = prevScrollColumn + , ... + } = app + + (* move LineGap and buffer to start of line *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Cursor.nextPairChr (buffer, cursorIdx) + in + if cursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Cursor.matchPair (buffer, cursorIdx) + in + if cursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val visualScrollColumn = + TextScroll.getScrollColumn + (buffer, cursorIdx, windowWidth, prevScrollColumn) + + val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer) + val startLine = + TextScroll.getStartLine + (prevLineNumber, cursorLine, windowHeight, #lineLength buffer) + + val buffer = LineGap.goToLine (startLine, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val drawMsg = [MailboxType.DRAW drawMsg] + in + NormalModeWith.bufferAndCursorIdx + ( app + , buffer + , cursorIdx + , NORMAL_MODE "" + , startLine + , searchList + , drawMsg + , bufferModifyTime + , visualScrollColumn + ) + end + end + end + + fun firstNonSpaceChr (app: app_type) = + let + val + { buffer + , cursorIdx + , windowWidth + , windowHeight + , startLine + , searchList + , bufferModifyTime + , ... + } = app + + (* move LineGap and buffer to start of line *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Cursor.vi0 (buffer, cursorIdx) + + (* move cursorIdx to first character on line *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Cursor.firstNonSpaceChr (buffer, cursorIdx) + in + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, searchList, [], bufferModifyTime) + end + + fun helpMoveToChr (app: app_type, buffer, cursorIdx, count, fMove, chr) = + if count = 0 then + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, #searchList app, [], #bufferModifyTime app) + else + let + (* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = fMove (buffer, cursorIdx, chr) + val newCount = if cursorIdx = newCursorIdx then 0 else count - 1 + in + helpMoveToChr (app, buffer, newCursorIdx, newCount, fMove, chr) + end + + fun moveToChr (app: app_type, count, fMove, chr) = + let val {cursorIdx, buffer, ...} = app + in helpMoveToChr (app, buffer, cursorIdx, count, fMove, chr) + end + + fun moveToNextMatch (app: app_type, count) = + let + val + { cursorIdx + , searchList + , buffer + , bufferModifyTime + , visualScrollColumn + , ... + } = app + val newCursorIdx = + PersistentVector.nextMatch (cursorIdx, searchList, count) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + NormalFinish.buildTextAndClear + (app, buffer, newCursorIdx, searchList, [], bufferModifyTime) + end + + fun moveToPrevMatch (app: app_type, count) = + let + val {cursorIdx, searchList, buffer, bufferModifyTime, ...} = app + val newCursorIdx = + PersistentVector.prevMatch (cursorIdx, searchList, count) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + NormalFinish.buildTextAndClear + (app, buffer, newCursorIdx, searchList, [], bufferModifyTime) + end + + fun toNextChr (app: app_type, count, chr) = + let + val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + NormalFinish.buildTextAndClear + (app, buffer, newCursorIdx, searchList, [], bufferModifyTime) + end + + fun tillNextChr (app: app_type, count, chr) = + let + val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + NormalFinish.buildTextAndClear + (app, buffer, newCursorIdx - 1, searchList, [], bufferModifyTime) + end + + fun toPrevChr (app: app_type, count, chr) = + let + val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + NormalFinish.buildTextAndClear + (app, buffer, newCursorIdx, searchList, [], bufferModifyTime) + end + + fun tillPrevChr (app: app_type, count, chr) = + let + val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + NormalFinish.buildTextAndClear + (app, buffer, newCursorIdx + 1, searchList, [], bufferModifyTime) + end +end diff --git a/shf/fcore/normal-mode/normal-search-finish.sml b/shf/fcore/normal-mode/normal-search-finish.sml new file mode 100644 index 0000000..4684a3c --- /dev/null +++ b/shf/fcore/normal-mode/normal-search-finish.sml @@ -0,0 +1,156 @@ +structure NormalSearchFinish = +struct + open AppType + open DrawMsg + + fun onSearchChanged + ( app: app_type + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + , buffer + ) = + let + val + { buffer + , cursorIdx + , startLine = prevLineNumber + , windowWidth + , windowHeight + , visualScrollColumn + , ... + } = app + + val searchScrollColumn = + TextScroll.getScrollColumnFromString + (searchCursorIdx, windowWidth, searchScrollColumn) + + val mode = NORMAL_SEARCH_MODE + { searchString = searchString + , tempSearchList = tempSearchList + , searchCursorIdx = searchCursorIdx + , searchScrollColumn = searchScrollColumn + , caseSensitive = caseSensitive + } + + val floatWindowWidth = Real32.fromInt windowWidth + val floatWindowHeight = Real32.fromInt windowHeight + + val searchStringPosY = windowHeight - TextConstants.ySpace - 5 + + val initialTextAcc = SearchBar.build + ( searchString + , 5 + , searchStringPosY + , windowWidth + , floatWindowWidth + , floatWindowHeight + , searchCursorIdx + , searchScrollColumn + , caseSensitive + ) + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer) + val startLine = + TextScroll.getStartLine + (prevLineNumber, cursorLine, windowHeight, #lineLength buffer) + val buffer = LineGap.goToLine (startLine, buffer) + + val remainingWindowHeight = windowHeight - (TextConstants.ySpace * 2) + + val drawMsg = NormalModeTextBuilder.startBuild + ( startLine + , cursorIdx + , buffer + , windowWidth + , remainingWindowHeight + , floatWindowWidth + , floatWindowHeight + , tempSearchList + , visualScrollColumn + , initialTextAcc + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val msgs = [MailboxType.DRAW drawMsg] + in + NormalSearchModeWith.changeTempSearchString + (app, buffer, startLine, mode, msgs) + end + + fun resize + ( app: app_type + , newWindowWidth + , newWindowHeight + , searchString + , searchCursorIdx + , tempSearchList + , searchScrollColumn + , caseSensitive + ) = + let + val + {buffer, cursorIdx, startLine = prevLineNumber, visualScrollColumn, ...} = + app + + val floatWindowWidth = Real32.fromInt newWindowWidth + val floatWindowHeight = Real32.fromInt newWindowHeight + + val searchScrollColumn = + TextScroll.getScrollColumnFromString + (searchCursorIdx, newWindowWidth, searchScrollColumn) + + val mode = NORMAL_SEARCH_MODE + { searchString = searchString + , tempSearchList = tempSearchList + , searchCursorIdx = searchCursorIdx + , searchScrollColumn = searchScrollColumn + , caseSensitive = caseSensitive + } + + val searchStringPosY = newWindowHeight - TextConstants.ySpace - 5 + + val initialTextAcc = SearchBar.build + ( searchString + , 5 + , searchStringPosY + , newWindowWidth + , floatWindowWidth + , floatWindowHeight + , searchCursorIdx + , searchScrollColumn + , caseSensitive + ) + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer) + val startLine = + TextScroll.getStartLine + (prevLineNumber, cursorLine, newWindowHeight, #lineLength buffer) + val buffer = LineGap.goToLine (startLine, buffer) + + val remainingWindowHeight = newWindowHeight - (TextConstants.ySpace * 2) + + val drawMsg = NormalModeTextBuilder.startBuild + ( startLine + , cursorIdx + , buffer + , newWindowWidth + , remainingWindowHeight + , floatWindowWidth + , floatWindowHeight + , tempSearchList + , visualScrollColumn + , initialTextAcc + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val msgs = [MailboxType.DRAW drawMsg] + in + NormalSearchModeWith.bufferAndSize + (app, mode, buffer, newWindowWidth, newWindowHeight, msgs) + end +end diff --git a/shf/fcore/normal-mode/normal-search-mode-with.sml b/shf/fcore/normal-mode/normal-search-mode-with.sml new file mode 100644 index 0000000..6f91dc4 --- /dev/null +++ b/shf/fcore/normal-mode/normal-search-mode-with.sml @@ -0,0 +1,140 @@ +structure NormalSearchModeWith = +struct + open AppType + + fun returnToNormalMode + ( app: app_type + , newBuffer + , newSearchList + , newStartLine + , newMode + , newDfa + , newMsgs + ) = + let + val + { mode = _ + , buffer = _ + , searchList = _ + , startLine = _ + , msgs = _ + , dfa = _ + , bufferModifyTime + , windowWidth + , windowHeight + , cursorIdx + , visualScrollColumn + } = app + in + { mode = newMode + , buffer = newBuffer + , searchList = newSearchList + , startLine = newStartLine + , dfa = newDfa + , bufferModifyTime = bufferModifyTime + , msgs = newMsgs + , windowWidth = windowWidth + , windowHeight = windowHeight + , cursorIdx = cursorIdx + , visualScrollColumn = visualScrollColumn + } + end + + fun changeTempSearchString + (app: app_type, newBuffer, newStartLine, newMode, newMsgs) = + let + val + { mode = _ + , buffer = _ + , searchList + , startLine = _ + , msgs = _ + , bufferModifyTime + , windowWidth + , windowHeight + , cursorIdx + , visualScrollColumn + , dfa + } = app + in + { mode = newMode + , buffer = newBuffer + , startLine = newStartLine + , msgs = newMsgs + , searchList = searchList + , bufferModifyTime = bufferModifyTime + , windowWidth = windowWidth + , windowHeight = windowHeight + , cursorIdx = cursorIdx + , visualScrollColumn = visualScrollColumn + , dfa = dfa + } + end + + fun searchList (app: app_type, newSearchList) = + let + val + { mode + , buffer + , searchList = _ + , startLine + , msgs + , bufferModifyTime + , windowWidth + , windowHeight + , cursorIdx + , visualScrollColumn + , dfa + } = app + in + { mode = mode + , searchList = newSearchList + , buffer = buffer + , startLine = startLine + , msgs = msgs + , bufferModifyTime = bufferModifyTime + , windowWidth = windowWidth + , windowHeight = windowHeight + , cursorIdx = cursorIdx + , visualScrollColumn = visualScrollColumn + , dfa = dfa + } + end + + fun bufferAndSize + ( app: app_type + , newMode + , newBuffer + , newWindowWidth + , newWindowHeight + , newMsgs + ) = + let + val + { mode = _ + , windowWidth = _ + , windowHeight = _ + , msgs = _ + , buffer = _ + , searchList + , startLine + , bufferModifyTime + , cursorIdx + , visualScrollColumn + , dfa + } = app + in + { mode = newMode + , buffer = newBuffer + , windowWidth = newWindowWidth + , windowHeight = newWindowHeight + , msgs = newMsgs + , searchList = searchList + , startLine = startLine + , bufferModifyTime = bufferModifyTime + , cursorIdx = cursorIdx + , visualScrollColumn = visualScrollColumn + , dfa = dfa + } + end +end diff --git a/shf/fcore/normal-mode/normal-search-mode.sml b/shf/fcore/normal-mode/normal-search-mode.sml new file mode 100644 index 0000000..2d68a75 --- /dev/null +++ b/shf/fcore/normal-mode/normal-search-mode.sml @@ -0,0 +1,274 @@ +structure NormalSearchMode = +struct + open AppType + open InputMsg + open MailboxType + + fun buildTempSearchList (searchString, buffer, cursorIdx, caseSensitive) = + let + val dfa = + if caseSensitive then CaseSensitiveDfa.fromString searchString + else CaseInsensitiveDfa.fromString searchString + in + SearchList.buildRange (buffer, cursorIdx + 1111, dfa) + end + + fun addChr + ( app: app_type + , searchString + , searchCursorIdx + , searchScrollColumn + , caseSensitive + , chr + ) = + let + val {cursorIdx, buffer, ...} = app + + val c = String.implode [chr] + val searchString = + if searchCursorIdx = String.size searchString then + searchString ^ c + else + let + val sub1 = Substring.extract (searchString, 0, SOME searchCursorIdx) + val sub2 = Substring.full c + val sub3 = Substring.extract (searchString, searchCursorIdx, NONE) + in + Substring.concat [sub1, sub2, sub3] + end + val searchCursorIdx = searchCursorIdx + 1 + + val buffer = LineGap.goToIdx (cursorIdx - 1111, buffer) + val (buffer, tempSearchList) = + buildTempSearchList (searchString, buffer, cursorIdx, caseSensitive) + in + NormalSearchFinish.onSearchChanged + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + , buffer + ) + end + + (* return to normal mode, keeping the same searchString and searchList + * from before entering this mode. *) + fun exitToNormalMode (app: app_type) = + let + val {buffer, cursorIdx, searchList, bufferModifyTime, ...} = app + in + NormalFinish.buildTextAndClear + (app, buffer, cursorIdx, searchList, [], bufferModifyTime) + end + + (* save search string and searchList and return to normal mode *) + fun saveSearch (app: app_type, searchString, caseSensitive, time) = + let + val + { buffer + , cursorIdx + , windowWidth + , windowHeight + , startLine + , visualScrollColumn + , ... + } = app + + val dfa = + if caseSensitive then CaseSensitiveDfa.fromString searchString + else CaseInsensitiveDfa.fromString searchString + + val buffer = LineGap.goToStart buffer + val (buffer, searchList) = SearchList.build (buffer, dfa) + + (* move LineGap to first line displayed on screen *) + val buffer = LineGap.goToLine (startLine, buffer) + + (* move buffer to new startLine as required by TextBuilder.build *) + val buffer = LineGap.goToLine (startLine, buffer) + + val drawMsg = NormalModeTextBuilder.build + ( startLine + , cursorIdx + , buffer + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) + val drawMsg = Vector.concat drawMsg + val drawMsg = DrawMsg.DRAW_TEXT drawMsg + val msgs = [DRAW drawMsg] + + val mode = NORMAL_MODE "" + in + NormalSearchModeWith.returnToNormalMode + (app, buffer, searchList, startLine, mode, dfa, msgs) + end + + fun backspace + ( app: app_type + , searchString + , tempSearchList + , searchScrollColumn + , searchCursorIdx + , caseSensitive + ) = + if searchCursorIdx = 0 then + app + else + let + val searchString = + if searchCursorIdx = String.size searchString then + String.substring (searchString, 0, String.size searchString - 1) + else + let + val sub1 = Substring.extract + (searchString, 0, SOME (searchCursorIdx - 1)) + val sub2 = Substring.extract (searchString, searchCursorIdx, SOME + (String.size searchString - searchCursorIdx)) + in + Substring.concat [sub1, sub2] + end + val searchCursorIdx = searchCursorIdx - 1 + + val {cursorIdx, buffer, ...} = app + val buffer = LineGap.goToIdx (cursorIdx - 1111, buffer) + val (buffer, tempSearchList) = + buildTempSearchList (searchString, buffer, cursorIdx, caseSensitive) + in + NormalSearchFinish.onSearchChanged + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + , buffer + ) + end + + fun moveLeft + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + ) = + if searchCursorIdx = 0 then + app + else + let + val searchCursorIdx = Int.max (0, searchCursorIdx - 1) + in + NormalSearchFinish.onSearchChanged + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + , #buffer app + ) + end + + fun moveRight + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + ) = + if searchCursorIdx = String.size searchString then + app + else + let + val searchCursorIdx = + Int.min (searchCursorIdx + 1, String.size searchString) + in + NormalSearchFinish.onSearchChanged + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + , #buffer app + ) + end + + fun update + ( app + , { searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + } + , msg + , time + ) = + case msg of + CHAR_EVENT chr => + addChr + ( app + , searchString + , searchCursorIdx + , searchScrollColumn + , caseSensitive + , chr + ) + | KEY_BACKSPACE => + backspace + ( app + , searchString + , tempSearchList + , searchScrollColumn + , searchCursorIdx + , caseSensitive + ) + | KEY_ESC => exitToNormalMode app + | KEY_ENTER => saveSearch (app, searchString, caseSensitive, time) + | ARROW_LEFT => + moveLeft + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + ) + | ARROW_RIGHT => + moveRight + ( app + , searchString + , tempSearchList + , searchCursorIdx + , searchScrollColumn + , caseSensitive + ) + | RESIZE_EVENT (width, height) => + NormalSearchFinish.resize + ( app + , width + , height + , searchString + , searchCursorIdx + , tempSearchList + , searchScrollColumn + , caseSensitive + ) + + (* In Vim's search mode, the up and down arrows can be used + * to scroll through the search history. + * I don't find this feature too useful as it is often easier to type + * the whole search string again, so I'm leaving it unimplemented + * until/unless I find that I wish this functionality was there + * while using the program. *) + | ARROW_UP => app + | ARROW_DOWN => app +end diff --git a/shf/fcore/normal-mode/normal-yank-delete.sml b/shf/fcore/normal-mode/normal-yank-delete.sml new file mode 100644 index 0000000..62dd6ab --- /dev/null +++ b/shf/fcore/normal-mode/normal-yank-delete.sml @@ -0,0 +1,14 @@ +structure NormalYankDelete = + MakeNormalDelete + (struct + open DrawMsg + open MailboxType + + fun initMsgs (low, length, buffer) = + let + val str = LineGap.substring (low, length + 1, buffer) + val msg = YANK str + in + [DRAW msg] + end + end) diff --git a/shf/fcore/normal-mode/normal-yank.sml b/shf/fcore/normal-mode/normal-yank.sml new file mode 100644 index 0000000..65f49e0 --- /dev/null +++ b/shf/fcore/normal-mode/normal-yank.sml @@ -0,0 +1,534 @@ +structure NormalYank = +struct + open AppType + open DrawMsg + open MailboxType + + fun finish (app, buffer, yankedString) = + let + val msgs = [DRAW (YANK yankedString)] + val mode = NORMAL_MODE "" + in + NormalModeWith.modeAndBuffer (app, buffer, mode, msgs) + end + + fun yankLeft (app: app_type, count) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val min = Cursor.vi0 (buffer, cursorIdx) + val low = Cursor.viH (buffer, cursorIdx, count) + + val low = Int.max (min, low) + val length = cursorIdx - low + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + + fun yankRight (app: app_type, count) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val endOfLineIdx = Cursor.viDlr (buffer, cursorIdx, 1) + 1 + val high = Cursor.viL (buffer, cursorIdx, count) + val high = Int.min (high, endOfLineIdx) + val length = high - cursorIdx + + val buffer = LineGap.goToIdx (high, buffer) + val str = LineGap.substring (cursorIdx, length, buffer) + in + finish (app, buffer, str) + end + + fun yankLineUp (app: app_type, count) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val cursorLineNumber = + if Cursor.isNextChrEndOfLine (buffer, cursorIdx) then + LineGap.idxToLineNumber (cursorIdx + 1, buffer) + else + LineGap.idxToLineNumber (cursorIdx, buffer) + val newCursorLineNumber = Int.max (cursorLineNumber - count, 0) + in + if cursorLineNumber = 0 then + NormalFinish.clearMode app + else if newCursorLineNumber = 0 then + let + val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1) + val buffer = LineGap.goToIdx (endOfLine, buffer) + + val endOfLine = + if Cursor.isCursorAtStartOfLine (buffer, endOfLine) then + endOfLine + 1 + else + endOfLine + 2 + + val buffer = LineGap.goToIdx (endOfLine, buffer) + val str = LineGap.substring (0, endOfLine, buffer) + in + finish (app, buffer, str) + end + else + let + val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1) + val buffer = LineGap.goToIdx (endOfLine, buffer) + val endsOnNewline = Cursor.isCursorAtStartOfLine (buffer, endOfLine) + + val endOfLine = if endsOnNewline then endOfLine else endOfLine + 1 + + val newCursorLineNumber = + if endsOnNewline andalso endOfLine = #textLength buffer - 1 then + newCursorLineNumber - 1 + else + newCursorLineNumber + val buffer = LineGap.goToLine (newCursorLineNumber, buffer) + + val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer) + val length = endOfLine - lineIdx + + val buffer = LineGap.goToIdx (endOfLine, buffer) + val str = LineGap.substring (lineIdx + 1, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankLineDown (app: app_type, count) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val startIdx = Cursor.vi0 (buffer, cursorIdx) + val buffer = LineGap.goToIdx (startIdx, buffer) + + val startLine = + if Cursor.isCursorAtStartOfLine (buffer, startIdx) then + LineGap.idxToLineNumber (startIdx, buffer) + else + LineGap.idxToLineNumber (startIdx + 1, buffer) + val endLine = startLine + count + 1 + + val buffer = LineGap.goToLine (endLine, buffer) + val endLineIdx = LineGap.lineNumberToIdx (endLine, buffer) + val buffer = LineGap.goToIdx (endLineIdx - 1, buffer) + + (* get "real" endLine by not considering newline after non-newline *) + val endLine = + if Cursor.isOnNewlineAfterChr (buffer, endLineIdx - 1) then + LineGap.idxToLineNumber (endLineIdx - 1, buffer) + else + LineGap.idxToLineNumber (endLineIdx, buffer) + in + if endLineIdx = #textLength buffer andalso endLine = startLine then + (* cursor is already on last line so don't yank *) + NormalFinish.clearMode app + else + let + val endLineIdx = endLineIdx + 1 + val length = endLineIdx - startIdx + + (* perform the actual yank *) + val buffer = LineGap.goToIdx (endLineIdx, buffer) + val str = LineGap.substring (startIdx, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankLine (app: app_type, count) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = Cursor.vi0 (buffer, cursorIdx) + + val buffer = LineGap.goToIdx (low, buffer) + val high = Cursor.viDlrForDelete (buffer, low, count) + + val buffer = LineGap.goToIdx (high, buffer) + val length = high - low + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + + fun yankToStartOfLine (app: app_type) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = Cursor.vi0 (buffer, cursorIdx) + + val length = cursorIdx - low + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + + fun yankWhenMovingBack (app: app_type, fMove, count) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = fMove (buffer, cursorIdx, count) + + val length = cursorIdx - low + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + + fun yankWhenMovingBackPlusOne (app: app_type, fMove, count) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = fMove (buffer, cursorIdx, count) + + val length = (cursorIdx + 1) - low + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + + fun yankWhenMovingForward (app: app_type, fMove, count) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val high = fMove (buffer, cursorIdx, count) + val beforeHigh = high - 1 + val buffer = LineGap.goToIdx (beforeHigh, buffer) + + val high = + if Cursor.isOnNewlineAfterChr (buffer, beforeHigh) then beforeHigh + else high + + val length = high - cursorIdx + val str = LineGap.substring (cursorIdx, length, buffer) + in + finish (app, buffer, str) + end + + fun yankToFirstNonSpaceChr (app: app_type) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val otherIdx = Cursor.vi0 (buffer, cursorIdx) + + val buffer = LineGap.goToIdx (otherIdx, buffer) + val otherIdx = Cursor.firstNonSpaceChr (buffer, otherIdx) + in + if cursorIdx > otherIdx then + (* yanking backwards from cursorIdx *) + let + val length = cursorIdx - otherIdx + 1 + val buffer = LineGap.goToIdx (otherIdx, buffer) + val str = LineGap.substring (otherIdx, length, buffer) + in + finish (app, buffer, str) + end + else if cursorIdx < otherIdx then + (* yanking forward from cursorIdx *) + let + val length = otherIdx - cursorIdx + val str = LineGap.substring (cursorIdx, length, buffer) + in + finish (app, buffer, str) + end + else + NormalFinish.clearMode app + end + + fun yankToEndOfText (app: app_type) = + let + val {buffer, cursorIdx, ...} = app + + val buffer = LineGap.goToEnd buffer + val {rightStrings, idx, ...} = buffer + val finishIdx = Int.max (0, idx - 1) + + val length = finishIdx - cursorIdx + val str = LineGap.substring (cursorIdx, length, buffer) + in + finish (app, buffer, str) + end + + fun yankToMatchingPair (app: app_type) = + let + val {buffer, cursorIdx, ...} = app + val otherIdx = Cursor.matchPair (buffer, cursorIdx) + in + if cursorIdx = otherIdx then + NormalFinish.clearMode app + else + let + val low = Int.min (cursorIdx, otherIdx) + val high = Int.max (cursorIdx, otherIdx) + val length = high - low + 1 + + val buffer = LineGap.goToIdx (high, buffer) + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankToNextMatch (app: app_type, count) = + let + val {cursorIdx, searchList, buffer, ...} = app + val high = PersistentVector.nextMatch (cursorIdx, searchList, count) + in + if high = ~1 orelse high <= cursorIdx then + NormalFinish.clearMode app + else + let + val length = high - cursorIdx + val buffer = LineGap.goToIdx (high, buffer) + val str = LineGap.substring (cursorIdx, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankToPrevMatch (app: app_type, count) = + let + val {cursorIdx, searchList, buffer, ...} = app + val low = PersistentVector.prevMatch (cursorIdx, searchList, count) + in + if low = ~1 orelse low >= cursorIdx then + NormalFinish.clearMode app + else + let + val length = cursorIdx - low + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + end + + fun helpYankToChr + (app: app_type, buffer, cursorIdx, otherIdx, count, fMove, fInc, chr) = + if count = 0 then + let + val low = Int.min (cursorIdx, otherIdx) + val high = Int.max (cursorIdx, otherIdx) + val length = high - low + + val buffer = LineGap.goToIdx (high, buffer) + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + else + let + val buffer = LineGap.goToIdx (otherIdx, buffer) + val newOtherIdx = fMove (buffer, otherIdx, chr) + val newCount = if newOtherIdx = otherIdx then 0 else count - 1 + val newOtherIdx = fInc (newOtherIdx, 1) + in + helpYankToChr + (app, buffer, cursorIdx, newOtherIdx, newCount, fMove, fInc, chr) + end + + fun yankToNextChr (app: app_type, count, chr) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val length = newCursorIdx - cursorIdx + 1 + val buffer = LineGap.goToIdx (newCursorIdx, buffer) + val str = LineGap.substring (cursorIdx, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankTillNextChr (app: app_type, count, chr) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val length = newCursorIdx - cursorIdx + val buffer = LineGap.goToIdx (newCursorIdx, buffer) + val str = LineGap.substring (cursorIdx, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankToPrevChr (app: app_type, count, chr) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val length = cursorIdx - newCursorIdx + val str = LineGap.substring (newCursorIdx, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankTillPrevChr (app: app_type, count, chr) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val newCursorIdx = + Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count}) + in + if newCursorIdx = ~1 then + NormalFinish.clearMode app + else + let + val newCursorIdx = newCursorIdx + 1 + val length = cursorIdx - newCursorIdx + val str = LineGap.substring (newCursorIdx, length, buffer) + in + finish (app, buffer, str) + end + end + + fun yankToStart (app: app_type) = + let + val {cursorIdx, buffer, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + + val high = Cursor.viDlrForDelete (buffer, cursorIdx, 1) + val buffer = LineGap.goToIdx (high, buffer) + val str = LineGap.substring (0, high, buffer) + in + finish (app, buffer, str) + end + + fun yankInsideWord (app: app_type) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = Cursor.prevWordStrict (buffer, cursorIdx, 1) + val high = Cursor.endOfWordStrict (buffer, cursorIdx, 1) + + val high = high + 1 + val buffer = LineGap.goToIdx (high, buffer) + val length = high - low + + val str = LineGap.substring (low, length, buffer) + in + if str = "\n" then NormalFinish.clearMode app + else finish (app, buffer, str) + end + + fun yankInsideWORD (app: app_type) = + let + val {buffer, cursorIdx, ...} = app + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val low = Cursor.prevWORDStrict (buffer, cursorIdx, 1) + val high = Cursor.endOfWORDStrict (buffer, cursorIdx, 1) + + val high = high + 1 + val buffer = LineGap.goToIdx (high, buffer) + val length = high - low + + val str = LineGap.substring (low, length, buffer) + in + if str = "\n" then NormalFinish.clearMode app + else finish (app, buffer, str) + end + + fun finishAfterYankInside (app: app_type, low, high, buffer) = + let + val length = high - low + val str = LineGap.substring (low, length, buffer) + in + finish (app, buffer, str) + end + + fun yankInsideChrOpen (app: app_type, chr) = + let + val {cursorIdx, buffer, ...} = app + + val start = cursorIdx + 1 + val buffer = LineGap.goToIdx (start, buffer) + + val low = Cursor.toPrevChr (buffer, start, {findChr = chr, count = 1}) + val buffer = LineGap.goToIdx (low, buffer) + val high = Cursor.matchPair (buffer, low) + val buffer = LineGap.goToIdx (high, buffer) + val low = low + 1 + in + if low = high then NormalFinish.clearMode app + else finishAfterYankInside (app, low, high, buffer) + end + + fun yankInsideChrClose (app: app_type, chr) = + let + val {cursorIdx, buffer, ...} = app + + val start = Int.max (cursorIdx - 1, 0) + val buffer = LineGap.goToIdx (start, buffer) + + val high = Cursor.toNextChr (buffer, start, {findChr = chr, count = 1}) + val buffer = LineGap.goToIdx (high, buffer) + val low = Cursor.matchPair (buffer, high) + 1 + in + if low = high then NormalFinish.clearMode app + else finishAfterYankInside (app, low, high, buffer) + end + + fun yankAroundChrOpen (app: app_type, chr) = + let + val {cursorIdx, buffer, ...} = app + + val start = cursorIdx + 1 + val buffer = LineGap.goToIdx (start, buffer) + + val low = Cursor.toPrevChr (buffer, start, {findChr = chr, count = 1}) + val buffer = LineGap.goToIdx (low, buffer) + val high = Cursor.matchPair (buffer, low) + 1 + val buffer = LineGap.goToIdx (high, buffer) + val low = low + in + if low = high then NormalFinish.clearMode app + else finishAfterYankInside (app, low, high, buffer) + end + + fun yankAroundChrClose (app: app_type, chr) = + let + val {cursorIdx, buffer, ...} = app + + val start = Int.max (cursorIdx - 1, 0) + val buffer = LineGap.goToIdx (start, buffer) + + val high = Cursor.toNextChr (buffer, start, {findChr = chr, count = 1}) + val buffer = LineGap.goToIdx (high, buffer) + val low = Cursor.matchPair (buffer, high) + val high = high + 1 + in + if low = high then NormalFinish.clearMode app + else finishAfterYankInside (app, low, high, buffer) + end +end diff --git a/shf/fcore/persistent-vector.sml b/shf/fcore/persistent-vector.sml new file mode 100644 index 0000000..c246e71 --- /dev/null +++ b/shf/fcore/persistent-vector.sml @@ -0,0 +1,937 @@ +structure PersistentVector = +struct + (* Clojure-style persistent vector, for building search list. + * There is an "int table" too, which stores the last index + * at the node with the same index. + * We can use the size table for binary search. + * *) + datatype t = + BRANCH of t vector * int vector + | LEAF of {start: int, finish: int} vector * int vector + + val maxSize = 32 + val halfSize = 16 + + fun isEmpty t = + case t of + LEAF (_, sizes) => Vector.length sizes = 0 + | BRANCH (_, sizes) => Vector.length sizes = 0 + + val empty = LEAF (#[], #[]) + + datatype append_result = APPEND of t | UPDATE of t + + fun isInRange (checkIdx, t) = + case t of + BRANCH (nodes, sizes) => + let + val searchIdx = BinSearch.equalOrMore (checkIdx, sizes) + in + if searchIdx = ~1 then + false + else if searchIdx = 0 then + isInRange (checkIdx, Vector.sub (nodes, searchIdx)) + else + let + val nextCheckIdx = checkIdx - Vector.sub (sizes, searchIdx - 1) + in + isInRange (nextCheckIdx, Vector.sub (nodes, searchIdx)) + end + end + | LEAF (values, sizes) => + let + val searchIdx = BinSearch.equalOrMore (checkIdx, sizes) + in + if searchIdx = ~1 then + false + else + let + val {start, finish} = Vector.sub (values, searchIdx) + in + checkIdx >= start andalso checkIdx <= finish + end + end + + fun getFinishIdx t = + case t of + BRANCH (_, sizes) => Vector.sub (sizes, Vector.length sizes - 1) + | LEAF (_, sizes) => Vector.sub (sizes, Vector.length sizes - 1) + + fun getStartIdx t = + case t of + BRANCH (nodes, _) => getStartIdx (Vector.sub (nodes, 0)) + | LEAF (items, _) => + if Vector.length items = 0 then + 0 + else + #start (Vector.sub (items, 0)) + + fun helpAppend (start, finish, tree) = + case tree of + BRANCH (nodes, sizes) => + let + val lastNode = Vector.sub (nodes, Vector.length nodes - 1) + val prevSize = + if Vector.length sizes > 1 then + Vector.sub (sizes, Vector.length sizes - 2) + else + 0 + in + case helpAppend (start - prevSize, finish - prevSize, lastNode) of + UPDATE newLast => + let + val lastPos = Vector.length nodes - 1 + val newNode = Vector.update (nodes, lastPos, newLast) + val newSizes = Vector.update (sizes, lastPos, finish) + val newNode = BRANCH (newNode, newSizes) + in + UPDATE newNode + end + | APPEND newVec => + if Vector.length nodes = maxSize then + let + (* adjust "finish" so that it does not consider + * offset for "lower" vector *) + val finish = finish - Vector.sub (sizes, Vector.length sizes - 1) + val newNode = BRANCH (#[newVec], #[finish]) + in + APPEND newNode + end + else + let + val newNodes = Vector.concat [nodes, #[newVec]] + val newSizes = Vector.concat [sizes, #[finish]] + val newNodes = BRANCH (newNodes, newSizes) + in + UPDATE newNodes + end + end + | LEAF (values, sizes) => + if Vector.length values + 1 > maxSize then + (* when we split a leaf into two vectors, + * we want to adjust the start and finish parameters + * so that they don't contain the offset relevant to the + * "lower" vector, which was split from *) + let + val prevFinish = Vector.sub (sizes, Vector.length sizes - 1) + val start = start - prevFinish + val finish = finish - prevFinish + val newNode = LEAF (#[{start = start, finish = finish}], #[finish]) + in + APPEND newNode + end + else + let + val newNode = Vector.concat + [values, #[{start = start, finish = finish}]] + val newSizes = Vector.concat [sizes, #[finish]] + val newNode = LEAF (newNode, newSizes) + in + UPDATE newNode + end + + fun append (start, finish, tree) = + case helpAppend (start, finish, tree) of + UPDATE t => t + | APPEND newNode => + let + val maxSize = getFinishIdx tree + in + BRANCH (#[tree, newNode], #[maxSize, finish]) + end + + fun getStart tree = + case tree of + LEAF (values, _) => Vector.sub (values, 0) + | BRANCH (nodes, _) => getStart (Vector.sub (nodes, 0)) + + fun helpNextMatch (cursorIdx, tree, absOffset) = + case tree of + LEAF (values, sizes) => + let + val idx = BinSearch.equalOrMore (cursorIdx, sizes) + in + if idx = ~1 then {start = ~1, finish = ~1} + else + let + val {start, finish} = Vector.sub (values, idx) + in + {start = start + absOffset, finish = finish + absOffset} + end + end + | BRANCH (nodes, sizes) => + let + val idx = BinSearch.equalOrMore (cursorIdx, sizes) + in + if idx = ~1 then + {start = ~1, finish = ~1} + else if idx = 0 then + helpNextMatch (cursorIdx, Vector.sub (nodes, idx), absOffset) + else + let + val prevSize = Vector.sub (sizes, idx - 1) + val cursorIdx = cursorIdx - prevSize + val absOffset = absOffset + prevSize + in + helpNextMatch (cursorIdx, Vector.sub (nodes, idx), absOffset) + end + end + + fun loopNextMatch (prevStart, prevFinish, tree, count) = + if count = 0 then + prevStart + else + let + val {start, finish} = helpNextMatch (prevFinish + 1, tree, 0) + in + if start = ~1 then + let val {start, finish} = getStart tree + in loopNextMatch (start, finish, tree, count - 1) + end + else + loopNextMatch (start, finish, tree, count - 1) + end + + fun nextMatch (cursorIdx, tree, count) = + if isEmpty tree then + ~1 + else + let + val {start, finish} = helpNextMatch (cursorIdx, tree, 0) + in + if start = ~1 then + let val {start, finish} = getStart tree + in loopNextMatch (start, finish, tree, count - 1) + end + else if cursorIdx >= start andalso cursorIdx <= finish then + loopNextMatch (start, finish, tree, count) + else + loopNextMatch (start, finish, tree, count - 1) + end + + fun getLast (tree, absOffset) = + case tree of + LEAF (values, _) => + let + val {start, finish} = Vector.sub (values, Vector.length values - 1) + in + {start = start + absOffset, finish = finish + absOffset} + end + | BRANCH (nodes, sizes) => + let + val prevSize = + if Vector.length sizes - 2 >= 0 then + Vector.sub (sizes, Vector.length sizes - 2) + else + 0 + val absOffset = absOffset + prevSize + in + getLast (Vector.sub (nodes, Vector.length nodes - 1), absOffset) + end + + (* slightly tricky. + * The `sizes` vector contains the last/finish position of the item + * at the corresponding index in the `nodes` or `values` vector + * However, what we when searching for the previous match + * is different: we want the node that has a start prior + * to the cursorIdx. + * This information cannot be retrieved with 100% accuracy + * using the `sizes` vector. + * To get what we want, we recurse downwards using the `sizes` vector. + * If we found the node we want, we return it. + * Otherwise, we return a state meaning "no node at this position" + * and we use the call stack to descend down the node at the previous index. + * There might not be a previous index because the current index is 0. + * In this case, either the call stack will handle it, + * or the caller to `helpPrevMatch` will. *) + fun helpPrevMatch (cursorIdx, tree, absOffset) = + case tree of + LEAF (values, sizes) => + let + val idx = BinSearch.equalOrMore (cursorIdx, sizes) + in + if idx < 0 then + {start = ~1, finish = ~1} + else if idx = 0 then + let + val {start, finish} = Vector.sub (values, 0) + in + if start < cursorIdx then + {start = start + absOffset, finish = finish + absOffset} + else + {start = ~1, finish = ~1} + end + else + let + val {start, finish} = Vector.sub (values, idx) + in + if cursorIdx > start then + {start = start + absOffset, finish = finish + absOffset} + else + let + val {start, finish} = Vector.sub (values, idx - 1) + in + {start = start + absOffset, finish = finish + absOffset} + end + end + end + | BRANCH (nodes, sizes) => + let + val idx = BinSearch.equalOrMore (cursorIdx, sizes) + in + if idx < 0 then + {start = ~1, finish = ~1} + else if idx = 0 then + helpPrevMatch (cursorIdx, Vector.sub (nodes, idx), absOffset) + else + let + val prevSize = Vector.sub (sizes, idx - 1) + val node = Vector.sub (nodes, idx) + val result = + helpPrevMatch (cursorIdx - prevSize, node, absOffset + prevSize) + in + if #start result = ~1 then + let + val prevSize = + if idx - 2 >= 0 then + Vector.sub (sizes, idx - 2) + else + 0 + val absOffset = absOffset + prevSize + in + getLast (Vector.sub (nodes, idx - 1), absOffset) + end + else result + end + end + + fun loopPrevMatch (prevStart, prevFinish, tree, count) = + if count = 0 then + prevStart + else + let + val {start, finish} = helpPrevMatch (prevFinish - 1, tree, 0) + in + if start = ~1 then + let val {start, finish} = getLast (tree, 0) + in loopPrevMatch (start, finish, tree, count - 1) + end + else + loopPrevMatch (start, finish, tree, count - 1) + end + + fun prevMatch (cursorIdx, tree, count) = + if isEmpty tree then + ~1 + else + let + val {start, finish} = helpPrevMatch (cursorIdx, tree, 0) + in + if start = ~1 then + let val {start, finish} = getLast (tree, 0) + in loopPrevMatch (start, finish, tree, count - 1) + end + else if cursorIdx >= start andalso cursorIdx <= finish then + loopPrevMatch (start, finish, tree, count) + else + loopPrevMatch (start, finish, tree, count - 1) + end + + fun splitLeft (splitIdx, tree) = + case tree of + LEAF (items, sizes) => + if Vector.length items = 0 then + (* if tree is empty, then just return tree *) + tree + else + let + val {start, ...} = Vector.sub (items, 0) + in + (* if all items are after splitIdx, + * then we want to return an empty tree, + * splitting everything *) + if splitIdx < start then + empty + else if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then + (* if all items are before splitIdx, + * then we want to return the same tree, + * splitting nothing *) + tree + else + (* we want to split from somewhere in middle, keeping left *) + let + val idx = BinSearch.equalOrMore (splitIdx, sizes) + val idx = SOME idx + + val items = VectorSlice.slice (items, 0, idx) + val items = VectorSlice.vector items + + val sizes = VectorSlice.slice (sizes, 0, idx) + val sizes = VectorSlice.vector sizes + in + LEAF (items, sizes) + end + end + | BRANCH (nodes, sizes) => + if Vector.length nodes = 0 then + tree + else + if splitIdx < Vector.sub (sizes, 0) then + (* we want to split first node from rest *) + splitLeft (splitIdx, Vector.sub (nodes, 0)) + else if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then + (* split point is after this subtree, + * so return this subtree unchanged *) + tree + else + (* we want to split from somewhere in middle *) + let + val idx = BinSearch.equalOrMore (splitIdx, sizes) + val prevSize = + if idx = 0 then + 0 + else + Vector.sub (sizes, idx - 1) + val child = + splitLeft (splitIdx - prevSize, Vector.sub (nodes, idx)) + + val sizes = VectorSlice.slice (sizes, 0, SOME idx) + val nodes = VectorSlice.slice (nodes, 0, SOME idx) + in + if isEmpty child then + let + val sizes = VectorSlice.vector sizes + val nodes = VectorSlice.vector nodes + in + BRANCH (nodes, sizes) + end + else + let + val childSize = VectorSlice.full #[getFinishIdx child + prevSize] + val sizes =VectorSlice.concat [sizes, childSize] + + val childNode = VectorSlice.full #[child] + val nodes = VectorSlice.concat [nodes, childNode] + in + BRANCH (nodes, sizes) + end + end + + (* When we split in this function, + * we always want to update the sizes vector + * so that the relative rope-like metadata is valid *) + fun splitRight (splitIdx, tree) = + case tree of + BRANCH (nodes, sizes) => + if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then + (* splitIdx is greater than largest element, + * so we want to remove everything; + * or, in other words, we want to return an empty vec *) + empty + else + let + val idx = BinSearch.equalOrMore (splitIdx, sizes) + val prevSize = + if idx = 0 then + 0 + else + Vector.sub (sizes, idx - 1) + + val oldChildSize = Vector.sub (sizes, idx) + val child = splitRight (splitIdx - prevSize, Vector.sub (nodes, idx)) + + val len = Vector.length nodes - (idx + 1) + val sizesSlice = VectorSlice.slice (sizes, idx + 1, SOME len) + val nodesSlice = VectorSlice.slice (nodes, idx + 1, SOME len) + in + if isEmpty child then + if VectorSlice.length sizesSlice = 0 then + (* if we descended down last node and last node became empty, + * then return empty vector *) + empty + else + let + val nodes = VectorSlice.vector nodesSlice + val sizes = VectorSlice.map (fn el => el - oldChildSize) sizesSlice + in + BRANCH (nodes, sizes) + end + else + let + val newChildSize = getFinishIdx child + val sizes = Vector.tabulate (VectorSlice.length sizesSlice + 1, + fn i => + if i = 0 then + newChildSize + else + let + val el = VectorSlice.sub (sizesSlice, i - 1) + in + el - oldChildSize + newChildSize + end + ) + + val child = VectorSlice.full #[child] + val nodes = VectorSlice.concat [child, nodesSlice] + in + BRANCH (nodes, sizes) + end + end + | LEAF (items, sizes) => + if Vector.length items = 0 then + tree + else + if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then + empty + else if splitIdx < #start (Vector.sub (items, 0)) then + tree + else + let + val idx = BinSearch.equalOrMore (splitIdx, sizes) + val {start, finish} = Vector.sub (items, idx) + val idx = + if splitIdx >= start then + idx + 1 + else + idx + in + if idx >= Vector.length items then + empty + else + let + val prevSize = + if idx > 0 then + Vector.sub (sizes, idx - 1) + else + 0 + val len = Vector.length items - idx + val itemsSlice = VectorSlice.slice (items, idx, SOME len) + val items = VectorSlice.map + (fn {start, finish} => + {start = start - prevSize, finish = finish - prevSize} + ) + itemsSlice + val sizes = Vector.map #finish items + in + LEAF (items, sizes) + end + end + + fun decrementBy (decBy, tree) = + case tree of + BRANCH (nodes, sizes) => + let + val child = decrementBy (decBy, Vector.sub (nodes, 0)) + val nodes = Vector.update (nodes, 0, child) + val sizes = Vector.map (fn sz => sz - decBy) sizes + in + BRANCH (nodes, sizes) + end + | LEAF (items, sizes) => + let + val items = Vector.map + (fn {start, finish} => + {start = start - decBy, finish = finish - decBy} + ) items + val sizes = Vector.map #finish items + in + LEAF (items, sizes) + end + + fun incrementBy (incBy, tree) = + case tree of + BRANCH (nodes, sizes) => + let + val child = incrementBy (incBy, Vector.sub (nodes, 0)) + val nodes = Vector.update (nodes, 0, child) + val sizes = Vector.map (fn sz => sz + incBy) sizes + in + BRANCH (nodes, sizes) + end + | LEAF (items, sizes) => + let + val items = Vector.map + (fn {start, finish} => + {start = start + incBy, finish = finish + incBy} + ) items + val sizes = Vector.map #finish items + in + LEAF (items, sizes) + end + + fun countDepthLoop (counter, tree) = + case tree of + BRANCH (nodes, _) => countDepthLoop (counter + 1, Vector.sub (nodes, 0)) + | LEAF (_, _) => counter + 1 + + fun countDepth tree = countDepthLoop (0, tree) + + datatype merge_same_depth_result = + MERGE_SAME_DEPTH_UPDATE of t + | MERGE_SAME_DEPTH_FULL + + fun mergeSameDepth (left, right) = + case (left, right) of + (LEAF (leftItems, leftSizes), LEAF (rightItems, rightSizes)) => + if Vector.length leftItems + Vector.length rightItems <= maxSize then + let + val offset = Vector.sub (leftSizes, Vector.length leftSizes - 1) + val newVecLen = Vector.length leftItems + Vector.length rightItems + val items = Vector.tabulate (newVecLen, + fn i => + if i < Vector.length leftItems then + Vector.sub (leftItems, i) + else + let + val {start, finish} = + Vector.sub (rightItems, i - Vector.length leftItems) + in + {start = start + offset, finish = finish + offset} + end + ) + val sizes = Vector.map #finish items + in + MERGE_SAME_DEPTH_UPDATE (LEAF (items, sizes)) + end + else + MERGE_SAME_DEPTH_FULL + | (BRANCH (leftNodes, leftSizes), BRANCH (rightNodes, rightSizes)) => + if Vector.length leftNodes + Vector.length rightNodes <= maxSize then + let + val offset = Vector.sub (leftSizes, Vector.length leftSizes - 1) + val nodes = Vector.concat [leftNodes, rightNodes] + + val sizes = Vector.tabulate (Vector.length nodes, + fn i => + if i < Vector.length leftSizes then + Vector.sub (leftSizes, i) + else + Vector.sub (rightSizes, i - Vector.length leftSizes) + offset + ) + in + MERGE_SAME_DEPTH_UPDATE (BRANCH (nodes, sizes)) + end + else + MERGE_SAME_DEPTH_FULL + | _ => + raise Fail "PersistentVector.mergeSameDepth: \ + \left and right should both be BRANCH or both be LEAF \ + \but one is BRANCH and one is LEAF" + + datatype merge_diff_depth_result = + MERGE_DIFF_DEPTH_UPDATE of t + | MERGE_DIFF_DEPTH_FULL + + fun mergeWhenRightDepthIsGreater (left, right, targetDepth, curDepth) = + if curDepth = targetDepth then + case mergeSameDepth (left, right) of + MERGE_SAME_DEPTH_UPDATE tree => MERGE_DIFF_DEPTH_UPDATE tree + | MERGE_SAME_DEPTH_FULL => MERGE_DIFF_DEPTH_FULL + else + case right of + BRANCH (nodes, sizes) => + (case mergeWhenRightDepthIsGreater + (left, Vector.sub (nodes, 0), targetDepth, curDepth + 1) of + MERGE_DIFF_DEPTH_UPDATE child => + let + val oldChildSize = Vector.sub (sizes, 0) + val newChildSize = getFinishIdx child + val difference = newChildSize - oldChildSize + + val nodes = Vector.update (nodes, 0, child) + val sizes = Vector.map (fn el => el + difference) sizes + in + MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes)) + end + | MERGE_DIFF_DEPTH_FULL => + let + val leftSize = getFinishIdx left + val sizes = Vector.tabulate (Vector.length nodes + 1, + fn i => + if i = 0 then + leftSize + else + Vector.sub (sizes, i - 1) + leftSize + ) + val nodes = Vector.concat [#[left], nodes] + in + MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes)) + end) + | LEAF _ => + raise Fail "PersistentVector.mergeWhenRightDepthIsGreater: \ + \reached LEAF before (curDepth = targetDepth)" + + fun mergeWhenLeftDepthIsGreater (left, right, targetDepth, curDepth) = + if targetDepth = curDepth then + case mergeSameDepth (left, right) of + MERGE_SAME_DEPTH_UPDATE tree => MERGE_DIFF_DEPTH_UPDATE tree + | MERGE_SAME_DEPTH_FULL => MERGE_DIFF_DEPTH_FULL + else + case left of + BRANCH (nodes, sizes) => + (case + mergeWhenLeftDepthIsGreater ( + Vector.sub (nodes, Vector.length nodes - 1), + right, + targetDepth, + curDepth + 1) of + MERGE_DIFF_DEPTH_UPDATE child => + let + val lastIdx = Vector.length sizes - 1 + val oldChildSize = Vector.sub (sizes, lastIdx) + val newChildSize = getFinishIdx child + val difference = newChildSize - oldChildSize + + val nodes = Vector.update (nodes, lastIdx, child) + val sizes = Vector.map (fn el => el + difference) sizes + in + MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes)) + end + | MERGE_DIFF_DEPTH_FULL => + let + val maxLeftSize = Vector.sub (sizes, Vector.length sizes - 1) + val rightSize = getFinishIdx right + maxLeftSize + val sizes = Vector.concat [sizes, #[rightSize]] + val nodes = Vector.concat [nodes, #[right]] + in + MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes)) + end) + | LEAF _ => + raise Fail "PersistentVector.mergeWhenLeftDepthIsGreater: \ + \reached LEAF before (curDepth = targetDepth)" + + fun merge (left, right) = + let + val leftDepth = countDepth left + val rightDepth = countDepth right + in + if leftDepth = rightDepth then + case mergeSameDepth (left, right) of + MERGE_SAME_DEPTH_UPDATE t => t + | MERGE_SAME_DEPTH_FULL => + let + val leftSize = getFinishIdx left + val sizes = #[leftSize, getFinishIdx right + leftSize] + val nodes = #[left, right] + in + BRANCH (nodes, sizes) + end + else if leftDepth < rightDepth then + let + val targetDepth = rightDepth - leftDepth + in + case mergeWhenRightDepthIsGreater + (left, right, targetDepth, 0) of + MERGE_DIFF_DEPTH_UPDATE t => t + | MERGE_DIFF_DEPTH_FULL => empty + end + else + let + val targetDepth = leftDepth - rightDepth + in + case mergeWhenLeftDepthIsGreater + (left, right, targetDepth, 0) of + MERGE_DIFF_DEPTH_UPDATE t => t + | MERGE_DIFF_DEPTH_FULL => empty + end + end + + fun delete (start, length, tree) = + if isEmpty tree then + empty + else + let + val finish = start + length + + val matchAfterFinish = nextMatch (finish, tree, 1) + val matchAfterFinish = + if matchAfterFinish < finish then + ~1 + else + matchAfterFinish + in + let + val left = splitLeft (start, tree) + val right = splitRight (finish, tree) + in + if isEmpty left andalso isEmpty right then + empty + else if isEmpty left then + (* just decrement right and return it *) + let + val rightStart = getStartIdx right + val shouldBeStartIdx = matchAfterFinish - length + val difference = rightStart - shouldBeStartIdx + in + if difference = 0 then + right + else + decrementBy (difference, right) + end + else if isEmpty right then + (* return left half without doing anything *) + left + else + (* decrement right, and then merge both together *) + let + val leftSize = getFinishIdx left + val rightStartRelative = getStartIdx right + val rightStartAbsolute = leftSize + rightStartRelative + + val shouldBeStartIdx = matchAfterFinish - length + val difference = rightStartAbsolute - shouldBeStartIdx + in + if difference = 0 then + merge (left, right) + else + let + val right = decrementBy (difference, right) + in + merge (left, right) + end + end + end + end + + (* Usually, when inserting, we want the absolute metadata + * to be adjusted appropriately. + * An insertion should cause the absolute metadata to increment. + * However, we sometimes want to insert a match without adjusting + * the absolute metadata in this way. + * We want to do this when deleting some part of the buffer + * would cause a new match to be found, for example. *) + fun insertMatchKeepingAbsoluteInddices (start, finish, tree) = + let + val matchAfterFinish = nextMatch (finish, tree, 1) + in + if matchAfterFinish <= finish then + (* no match after the 'finish', so we can just append to 'tree' *) + append (start, finish, tree) + else + let + val left = splitLeft (start, tree) + val right = splitRight (finish, tree) + + val left = append (start, finish, left) + + val rightStartRelative = getStartIdx right + val rightStartAbsolute = rightStartRelative + finish + val difference = rightStartAbsolute - matchAfterFinish + val right = decrementBy (difference, right) + in + merge (left, right) + end + end + + fun extendExistingMatch (start, newFinish, tree) = + let + val matchAfterFinish = nextMatch (newFinish, tree, 1) + val left = splitLeft (start, tree) + val left = append (start, newFinish, left) + in + if matchAfterFinish <= newFinish then + (* no match after newFinish, so we can return 'left' + * which has the newFinish appended *) + left + else + let + val right = splitRight (newFinish, tree) + + val leftFinish = getFinishIdx left + val rightStartRelative = getStartIdx right + + val rightStartAbsolute = rightStartRelative + leftFinish + val difference = rightStartAbsolute - matchAfterFinish + val right = decrementBy (difference, right) + in + merge (left, right) + end + end + + (* functions only for testing *) + fun childrenHaveSameDepth (pos, nodes, expectedDepth) = + if pos = Vector.length nodes then + true + else + let + val node = Vector.sub (nodes, pos) + in + if allLeavesAtSameDepth node then + let + val nodeDepth = countDepth node + in + if nodeDepth = expectedDepth then + childrenHaveSameDepth (pos + 1, nodes, expectedDepth) + else + false + end + else + false + end + + and allLeavesAtSameDepth tree = + case tree of + BRANCH (nodes, _) => + let + val expectedDepth = countDepth (Vector.sub (nodes, 0)) + in + childrenHaveSameDepth (0, nodes, expectedDepth) + end + | LEAF _ => true + + fun fromListLoop (lst, acc) = + case lst of + {start, finish} :: tl => + let + val acc = append (start, finish, acc) + in + fromListLoop (tl, acc) + end + | [] => acc + + fun fromList coords = fromListLoop (coords, empty) + + fun toListLoop (tree, acc) = + case tree of + BRANCH (nodes, _) => + let + fun branchLoop (pos, acc) = + if pos = Vector.length nodes then + acc + else + let + val acc = toListLoop (Vector.sub (nodes, pos), acc) + in + branchLoop (pos + 1, acc) + end + in + branchLoop (0, acc) + end + | LEAF (items, _) => + let + fun itemLoop (pos, acc, offset) = + if pos = Vector.length items then + acc + else + let + val {start, finish} = Vector.sub (items, pos) + val item = {start = start + offset, finish = finish + offset} + in + itemLoop (pos + 1, item :: acc, offset) + end + + val offset = + case acc of + {finish, ...} :: _ => finish + | [] => 0 + in + itemLoop (0, acc, offset) + end + + fun toList tree = + let + val result = toListLoop (tree, []) + in + List.rev result + end +end diff --git a/shf/fcore/pipe-cursor.sml b/shf/fcore/pipe-cursor.sml new file mode 100644 index 0000000..b46aa82 --- /dev/null +++ b/shf/fcore/pipe-cursor.sml @@ -0,0 +1,41 @@ +structure PipeCursor = +struct + fun xToNdc (xOffset, xpos, scale, halfWidth) = + ((xpos * scale + xOffset) - halfWidth) / halfWidth + + fun yToNdc (yOffset, ypos, scale, halfHeight) = + ~(((ypos * scale + yOffset) - halfHeight) / halfHeight) + + fun lerp (xOffset: Real32.real, yOffset, z, scale, windowWidth, windowHeight, r, g, b) = + let + val halfWidth = windowWidth / 2.0 + val halfHeight = windowHeight / 2.0 + in + #[ +xToNdc (xOffset, 0.000000000000000, scale, halfWidth), +yToNdc (yOffset, 13.000000000000000, scale, halfHeight), +z, +r, g, b, +xToNdc (xOffset, 1.000000000000000, scale, halfWidth), +yToNdc (yOffset, 13.000000000000000, scale, halfHeight), +z, +r, g, b, +xToNdc (xOffset, 0.000000000000000, scale, halfWidth), +yToNdc (yOffset, 0.000000000000000, scale, halfHeight), +z, +r, g, b, +xToNdc (xOffset, 0.000000000000000, scale, halfWidth), +yToNdc (yOffset, 0.000000000000000, scale, halfHeight), +z, +r, g, b, +xToNdc (xOffset, 1.000000000000000, scale, halfWidth), +yToNdc (yOffset, 13.000000000000000, scale, halfHeight), +z, +r, g, b, +xToNdc (xOffset, 1.000000000000000, scale, halfWidth), +yToNdc (yOffset, 0.000000000000000, scale, halfHeight), +z, +r, g, b + ] + end +end diff --git a/shf/fcore/rect.sml b/shf/fcore/rect.sml new file mode 100644 index 0000000..c9dfebb --- /dev/null +++ b/shf/fcore/rect.sml @@ -0,0 +1,35 @@ +structure Rect = +struct + fun xToNdc (xOffset, xpos, scale, halfWidth) = + ((xpos * scale + xOffset) - halfWidth) / halfWidth + + fun yToNdc (yOffset, ypos, scale, halfHeight) = + ~(((ypos * scale + yOffset) - halfHeight) / halfHeight) + + fun lerp (xOffset: Real32.real, yOffset, z, scale, windowWidth, windowHeight, r, g, b) = + let + val halfWidth = windowWidth / 2.0 + val halfHeight = windowHeight / 2.0 + in + #[ +xToNdc (xOffset, 0.000000000000000, scale, halfWidth), +yToNdc (yOffset, 13.000000000000000, scale, halfHeight), +z, r, g, b, +xToNdc (xOffset, 7.000000000000000, scale, halfWidth), +yToNdc (yOffset, 13.000000000000000, scale, halfHeight), +z, r, g, b, +xToNdc (xOffset, 0.000000000000000, scale, halfWidth), +yToNdc (yOffset, 0.000000000000000, scale, halfHeight), +z, r, g, b, +xToNdc (xOffset, 0.000000000000000, scale, halfWidth), +yToNdc (yOffset, 0.000000000000000, scale, halfHeight), +z, r, g, b, +xToNdc (xOffset, 7.000000000000000, scale, halfWidth), +yToNdc (yOffset, 13.000000000000000, scale, halfHeight), +z, r, g, b, +xToNdc (xOffset, 7.000000000000000, scale, halfWidth), +yToNdc (yOffset, 0.000000000000000, scale, halfHeight), +z, r, g, b + ] + end +end diff --git a/shf/fcore/search-list/dfa-gen.sml b/shf/fcore/search-list/dfa-gen.sml new file mode 100644 index 0000000..800a145 --- /dev/null +++ b/shf/fcore/search-list/dfa-gen.sml @@ -0,0 +1,928 @@ +signature DFA_GEN_PARAMS = +sig + val endMarker: char + val charIsEqual: char * char -> bool +end + +signature DFA_GEN = +sig + type dfa = int vector vector + type dfa_state = int + + val fromString: string -> dfa + + val nextState: dfa * dfa_state * char -> dfa_state + val isFinal: dfa * dfa_state -> bool + val isDead: dfa_state -> bool + + val matchString: dfa * string -> (int * int) list +end + +functor MakeDfaGen(Fn: DFA_GEN_PARAMS): DFA_GEN = +struct + datatype parse_tree = + CHAR_LITERAL of {char: char, position: int} + | WILDCARD of int + | IS_ANY_CHARACTER of {chars: char vector, position: int} + | NOT_ANY_CHARACTER of {chars: char vector, position: int} + | CONCAT of + {l: parse_tree, r: parse_tree, leftMaxState: int, rightMaxState: int} + | ALTERNATION of + {l: parse_tree, r: parse_tree, leftMaxState: int, rightMaxState: int} + | ZERO_OR_ONE of parse_tree + | ZERO_OR_MORE of parse_tree + | ONE_OR_MORE of parse_tree + | GROUP of parse_tree + + fun isNullable tree = + case tree of + CHAR_LITERAL _ => false + | WILDCARD _ => false + | IS_ANY_CHARACTER _ => false + | NOT_ANY_CHARACTER _ => false + + | CONCAT {l, r, ...} => isNullable l andalso isNullable r + | ALTERNATION {l, r, ...} => isNullable l orelse isNullable r + + | ZERO_OR_ONE _ => true + | ZERO_OR_MORE _ => true + | ONE_OR_MORE regex => isNullable regex + + | GROUP regex => isNullable regex + + fun firstpos (tree, acc) = + case tree of + CHAR_LITERAL {position, ...} => position :: acc + | IS_ANY_CHARACTER {position, ...} => position :: acc + | NOT_ANY_CHARACTER {position, ...} => position :: acc + | WILDCARD i => i :: acc + + | CONCAT {l, r, ...} => + if isNullable l then + let val acc = firstpos (l, acc) + in firstpos (r, acc) + end + else + firstpos (l, acc) + | ALTERNATION {l, r, ...} => + let val acc = firstpos (l, acc) + in firstpos (r, acc) + end + + | ZERO_OR_ONE regex => firstpos (regex, acc) + | ZERO_OR_MORE regex => firstpos (regex, acc) + | ONE_OR_MORE regex => firstpos (regex, acc) + | GROUP regex => firstpos (regex, acc) + + fun lastpos (tree, acc) = + case tree of + CHAR_LITERAL {position, ...} => position :: acc + | IS_ANY_CHARACTER {position, ...} => position :: acc + | NOT_ANY_CHARACTER {position, ...} => position :: acc + | WILDCARD i => i :: acc + + | CONCAT {l, r, ...} => + if isNullable r then + let val acc = lastpos (l, acc) + in lastpos (r, acc) + end + else + lastpos (r, acc) + | ALTERNATION {l, r, ...} => + let val acc = lastpos (l, acc) + in lastpos (r, acc) + end + + | ZERO_OR_ONE regex => lastpos (regex, acc) + | ZERO_OR_MORE regex => lastpos (regex, acc) + | ONE_OR_MORE regex => lastpos (regex, acc) + | GROUP regex => lastpos (regex, acc) + + structure Set = + struct + datatype 'a set = BRANCH of 'a set * int * 'a * 'a set | LEAF + + fun isEmpty set = + case set of + BRANCH _ => false + | LEAF => true + + fun insertOrReplace (newKey, newVal, tree) = + case tree of + BRANCH (l, curKey, curVal, r) => + if newKey > curKey then + let val r = insertOrReplace (newKey, newVal, r) + in BRANCH (l, curKey, curVal, r) + end + else if newKey < curKey then + let val l = insertOrReplace (newKey, newVal, l) + in BRANCH (l, curKey, curVal, r) + end + else + BRANCH (l, newKey, newVal, r) + | LEAF => BRANCH (LEAF, newKey, newVal, LEAF) + + fun addFromList (lst, tree) = + case lst of + [] => tree + | k :: tl => + let val tree = insertOrReplace (k, (), tree) + in addFromList (tl, tree) + end + + fun getOrDefault (findKey, tree, default) = + case tree of + BRANCH (l, curKey, curVal, r) => + if findKey > curKey then getOrDefault (findKey, r, default) + else if findKey < curKey then getOrDefault (findKey, l, default) + else curVal + | LEAF => default + + fun helpToList (tree, acc) = + case tree of + BRANCH (l, curKey, curVal, r) => + let + val acc = helpToList (r, acc) + val acc = (curKey, curVal) :: acc + in + helpToList (l, acc) + end + | LEAF => acc + + fun toList tree = helpToList (tree, []) + + fun helpKeysToList (tree, acc) = + case tree of + BRANCH (l, curKey, _, r) => + let + val acc = helpKeysToList (r, acc) + val acc = curKey :: acc + in + helpKeysToList (l, acc) + end + | LEAF => acc + + fun keysToList tree = helpKeysToList (tree, []) + + fun helpValuesToList (tree, acc) = + case tree of + BRANCH (l, _, v, r) => + let + val acc = helpValuesToList (r, acc) + val acc = v :: acc + in + helpValuesToList (l, acc) + end + | LEAF => acc + + fun valuesToList tree = helpValuesToList (tree, []) + + fun map (f, tree) = + case tree of + BRANCH (l, key, value, r) => + let + val r = map (f, r) + val l = map (f, l) + val value = f value + in + BRANCH (l, key, value, r) + end + | LEAF => LEAF + + fun foldl (f, tree, acc) = + case tree of + BRANCH (l, k, v, r) => + let + val acc = foldl (f, l, acc) + val acc = f (v, acc) + in + foldl (f, r, acc) + end + | LEAF => acc + + fun foldr (f, tree, acc) = + case tree of + BRANCH (l, k, v, r) => + let + val acc = foldr (f, r, acc) + val acc = f (v, acc) + in + foldr (f, l, acc) + end + | LEAF => acc + end + + structure ParseDfa = + struct + (* parsing through precedence climbing algorithm. *) + val postfixLevel = 1 + val concatLevel = 2 + val altLevel = 3 + + local + fun loop (pos, str, openParens, closeParens) = + if pos = String.size str then + NONE + else + case String.sub (str, pos) of + #"(" => loop (pos + 1, str, openParens + 1, closeParens) + | #")" => + if closeParens + 1 = openParens then SOME pos + else loop (pos + 1, str, openParens, closeParens + 1) + | _ => loop (pos + 1, str, openParens, closeParens) + in + fun getRightParenIdx (pos, str) = loop (pos, str, 1, 0) + end + + (* assumes previous char is a backslash *) + fun isValidEscapeSequence chr = + case chr of + (* regex metacharacters *) + #"(" => (true, chr) + | #")" => (true, chr) + | #"[" => (true, chr) + | #"]" => (true, chr) + | #"+" => (true, chr) + | #"*" => (true, chr) + | #"|" => (true, chr) + | #"?" => (true, chr) + | #"." => (true, chr) + | #"-" => (true, chr) + (* standard escape sequences *) + | #"a" => (true, #"\a") + | #"b" => (true, #"\b") + | #"t" => (true, #"\t") + | #"n" => (true, #"\n") + | #"v" => (true, #"\v") + | #"f" => (true, #"\f") + | #"r" => (true, #"\r") + | #"\\" => (true, chr) + | _ => (false, chr) + + fun getCharsBetween (lowChr, highChr, acc) = + if lowChr = highChr then + highChr :: acc + else + let + val acc = lowChr :: acc + val lowChr = Char.succ lowChr + in + getCharsBetween (lowChr, highChr, acc) + end + + fun getCharsInBrackets (pos, str, acc) = + if pos = String.size str then + NONE + else + case String.sub (str, pos) of + #"\\" => + (* escape sequences *) + if pos + 1 = String.size str then + NONE + else + let + val chr = String.sub (str, pos + 1) + val (isValid, chr) = isValidEscapeSequence chr + in + if isValid then + (* Edge case: + * We have to check if there is a char range like a-z, + * and if there is, + * we have to check if the second char in the range + * is another escaped-character *) + if + pos + 2 < String.size str + andalso String.sub (str, pos + 2) = #"-" + andalso pos + 3 < String.size str + then + (* we do have a character range, + * which may possibly be escaped *) + case String.sub (str, pos + 3) of + #"(" => NONE + | #")" => NONE + | #"[" => NONE + | #"]" => NONE + | #"+" => NONE + | #"*" => NONE + | #"|" => NONE + | #"?" => NONE + | #"." => NONE + | #"-" => NONE + | #"\\" => + if pos + 4 < String.size str then + let + val chr2 = String.sub (str, pos + 4) + val (isValid, chr2) = isValidEscapeSequence chr2 + val acc = + if chr < chr2 then + getCharsBetween (chr, chr2, acc) + else + getCharsBetween (chr2, chr, acc) + in + getCharsInBrackets (pos + 5, str, acc) + end + else + NONE + | chr2 => + let + val acc = + if chr < chr2 then getCharsBetween (chr, chr2, acc) + else getCharsBetween (chr2, chr, acc) + in + getCharsInBrackets (pos + 4, str, acc) + end + else + (* no character range we have to check *) + getCharsInBrackets (pos + 2, str, chr :: acc) + else + NONE + end + | #"]" => + let val chars = Vector.fromList acc + in SOME (pos + 1, chars) + end + | chr => + if + pos + 1 < String.size str andalso String.sub (str, pos + 1) = #"-" + andalso pos + 2 < String.size str + then + (* handle character ranges like a-z. + * There are edge cases regarding + * the second character in the range. + * We have to check that any unescaped metacharacters + * return an invalid parse state. + * We also have to unescape any escape sequences. + * *) + case String.sub (str, pos + 2) of + #"\\" => + (* second char contains an escape sequence *) + if pos + 3 < String.size str then + let + val chr2 = String.sub (str, pos + 3) + val (isValid, chr2) = isValidEscapeSequence chr2 + val acc = + if chr < chr2 then getCharsBetween (chr, chr2, acc) + else getCharsBetween (chr2, chr, acc) + in + if isValid then getCharsInBrackets (pos + 4, str, acc) + else NONE + end + else + NONE + | #"(" => NONE + | #")" => NONE + | #"[" => NONE + | #"]" => NONE + | #"+" => NONE + | #"*" => NONE + | #"|" => NONE + | #"?" => NONE + | #"." => NONE + | #"-" => NONE + | chr2 => + (* valid char range *) + let + val acc = + if chr < chr2 then getCharsBetween (chr, chr2, acc) + else getCharsBetween (chr2, chr, acc) + in + getCharsInBrackets (pos + 3, str, acc) + end + else + getCharsInBrackets (pos + 1, str, chr :: acc) + + fun parseCharacterClass (pos, str, stateNum) = + case getCharsInBrackets (pos, str, []) of + SOME (pos, chars) => + let + val node = IS_ANY_CHARACTER {chars = chars, position = stateNum + 1} + in + SOME (pos, node, stateNum + 1) + end + | NONE => NONE + + fun parseNegateCharacterClass (pos, str, stateNum) = + case getCharsInBrackets (pos, str, []) of + SOME (pos, chars) => + let + val node = + NOT_ANY_CHARACTER {chars = chars, position = stateNum + 1} + in + SOME (pos, node, stateNum + 1) + end + | NONE => NONE + + fun computeAtom (pos, str, stateNum) = + if pos = String.size str then + NONE + else + case String.sub (str, pos) of + #"(" => + (case getRightParenIdx (pos + 1, str) of + SOME groupEndIdx => + let + val substr = String.substring + (str, pos + 1, groupEndIdx - pos - 1) + in + case parse (substr, stateNum) of + SOME (rhs, stateNum) => + SOME (groupEndIdx + 1, rhs, stateNum) + | NONE => NONE + end + | NONE => NONE) + | #"\\" => + (* escape sequences *) + if pos + 1 = String.size str then + NONE + else + let + val chr = String.sub (str, pos + 1) + val (isValid, chr) = isValidEscapeSequence chr + in + if Fn.charIsEqual (chr, Fn.endMarker) then + NONE + else if isValid then + let + val chr = CHAR_LITERAL {char = chr, position = stateNum + 1} + in + SOME (pos + 2, chr, stateNum + 1) + end + else + NONE + end + | #"." => + let val w = WILDCARD (stateNum + 1) + in SOME (pos + 1, w, stateNum + 1) + end + | #"[" => + if pos + 1 = String.size str then + NONE + else if String.sub (str, pos + 1) = #"^" then + parseNegateCharacterClass (pos + 2, str, stateNum) + else + parseCharacterClass (pos + 1, str, stateNum) + | #")" => NONE + | #"]" => NONE + | #"+" => NONE + | #"*" => NONE + | #"|" => NONE + | #"?" => NONE + | #"-" => NONE + | chr => + if Fn.charIsEqual (chr, Fn.endMarker) then + NONE + else + let val chr = CHAR_LITERAL {char = chr, position = stateNum + 1} + in SOME (pos + 1, chr, stateNum + 1) + end + + and climb (pos, str, lhs, level, stateNum) : (int * parse_tree * int) option = + if pos = String.size str then + SOME (pos, lhs, stateNum) + else + case String.sub (str, pos) of + #"|" => + if level < altLevel then + SOME (pos, lhs, stateNum) + else if pos + 1 < String.size str then + let + val chr = String.sub (str, pos + 1) + val chr = CHAR_LITERAL {char = chr, position = stateNum + 1} + in + case climb (pos + 2, str, chr, altLevel, stateNum + 1) of + SOME (pos, rhs, rightStateNum) => + let + val result = ALTERNATION + { l = lhs + , r = rhs + , leftMaxState = stateNum + , rightMaxState = rightStateNum + } + in + SOME (pos, result, rightStateNum) + end + | NONE => NONE + end + else + NONE + | #"?" => + if level < postfixLevel then + SOME (pos, lhs, stateNum) + else + let val lhs = ZERO_OR_ONE lhs + in climb (pos + 1, str, lhs, postfixLevel, stateNum) + end + | #"*" => + if level < postfixLevel then + SOME (pos, lhs, stateNum) + else + let val lhs = ZERO_OR_MORE lhs + in climb (pos + 1, str, lhs, postfixLevel, stateNum) + end + | #"+" => + if level < postfixLevel then + SOME (pos, lhs, stateNum) + else + let val lhs = ONE_OR_MORE lhs + in climb (pos + 1, str, lhs, postfixLevel, stateNum) + end + | chr => + if level < concatLevel then + SOME (pos, lhs, stateNum) + else + case computeAtom (pos, str, stateNum) of + SOME (nextPos, curAtom, atomStateNum) => + (case climb (nextPos, str, curAtom, concatLevel, atomStateNum) of + SOME (pos, rhs, rightStateNum) => + let + val result = CONCAT + { l = lhs + , r = rhs + , leftMaxState = stateNum + , rightMaxState = rightStateNum + } + in + SOME (pos, result, rightStateNum) + end + | NONE => NONE) + | NONE => NONE + + and loop (pos, str, ast, stateNum) = + if pos = String.size str then + SOME (ast, stateNum) + else + case climb (pos, str, ast, altLevel, stateNum) of + SOME (pos, ast, stateNum) => loop (pos, str, ast, stateNum) + | NONE => NONE + + and parse (str, stateNum) = + if String.size str > 0 then + case computeAtom (0, str, stateNum) of + SOME (nextPos, lhs, stateNum) => loop (nextPos, str, lhs, stateNum) + | NONE => NONE + else + NONE + end + + structure ToDfa = + struct + type dstate_element = {marked: bool, transitions: int list} + type dstate_vec = dstate_element vector + + fun chrExistsInVec (idx, vec, curChr) = + if idx = Vector.length vec then + false + else + let + val idxChr = Vector.sub (vec, idx) + in + Fn.charIsEqual (idxChr, curChr) + orelse chrExistsInVec (idx + 1, vec, curChr) + end + + fun addKeysToFollowSet (lst, addSet, followSet) = + case lst of + hd :: tl => + let + val currentFollows = Set.getOrDefault (hd, followSet, []) + val updatedFollows = Set.addFromList (currentFollows, addSet) + val updatedFollows: int list = Set.keysToList updatedFollows + val followSet = Set.insertOrReplace (hd, updatedFollows, followSet) + in + addKeysToFollowSet (tl, addSet, followSet) + end + | [] => followSet + + fun addToFollowSet (tree, followSet) = + case tree of + WILDCARD _ => followSet + | CHAR_LITERAL {char, position} => + (* we add the endMarker and its position to the followSet *) + if char = Fn.endMarker then + Set.insertOrReplace (position, [Char.ord Fn.endMarker], followSet) + else + followSet + | IS_ANY_CHARACTER _ => followSet + | NOT_ANY_CHARACTER _ => followSet + | CONCAT {l, r, ...} => + let + val followSet = addToFollowSet (l, followSet) + val followSet = addToFollowSet (r, followSet) + + val lpOfLeft = lastpos (l, []) + val fpOfRight = firstpos (r, []) + val fpOfRight = Set.addFromList (fpOfRight, Set.LEAF) + in + addKeysToFollowSet (lpOfLeft, fpOfRight, followSet) + end + | ALTERNATION {l, r, ...} => + let val followSet = addToFollowSet (l, followSet) + in addToFollowSet (r, followSet) + end + | ZERO_OR_MORE child => + let + val followSet = addToFollowSet (child, followSet) + val fp = firstpos (child, []) + val fp = Set.addFromList (fp, Set.LEAF) + val lp = lastpos (child, []) + in + addKeysToFollowSet (lp, fp, followSet) + end + | ONE_OR_MORE child => + let + val followSet = addToFollowSet (child, followSet) + val lp = lastpos (child, []) + val fp = firstpos (child, []) + val fp = Set.addFromList (fp, Set.LEAF) + in + addKeysToFollowSet (lp, fp, followSet) + end + | ZERO_OR_ONE child => addToFollowSet (child, followSet) + | GROUP child => addToFollowSet (child, followSet) + + fun appendIfNew (pos, dstates, newStates) = + if pos = Vector.length dstates then + let + val record = {transitions = newStates, marked = false} + val dstates = Vector.concat [dstates, Vector.fromList [record]] + in + (pos, dstates) + end + else + let + val {transitions: int list, marked = _} = Vector.sub (dstates, pos) + in + if transitions = newStates then (pos, dstates) + else appendIfNew (pos + 1, dstates, newStates) + end + + fun getUnmarkedTransitionsIfExists (pos, dstates) = + if pos = Vector.length dstates then + NONE + else + let + val record: dstate_element = Vector.sub (dstates, pos) + in + if #marked record then + getUnmarkedTransitionsIfExists (pos + 1, dstates) + else + SOME (pos, #transitions record) + end + + fun isCharMatch (regex, pos, curChr) = + case regex of + CHAR_LITERAL {char, ...} => Fn.charIsEqual (char, curChr) + | WILDCARD _ => true + | IS_ANY_CHARACTER {chars, ...} => chrExistsInVec (0, chars, curChr) + | NOT_ANY_CHARACTER {chars, ...} => + let val charIsValid = chrExistsInVec (0, chars, curChr) + in not charIsValid + end + | ALTERNATION {l, r, leftMaxState, ...} => + if pos > leftMaxState then isCharMatch (r, pos, curChr) + else isCharMatch (l, pos, curChr) + | CONCAT {l, r, leftMaxState, ...} => + if pos > leftMaxState then isCharMatch (r, pos, curChr) + else isCharMatch (l, pos, curChr) + | ZERO_OR_ONE child => isCharMatch (child, pos, curChr) + | ZERO_OR_MORE child => isCharMatch (child, pos, curChr) + | ONE_OR_MORE child => isCharMatch (child, pos, curChr) + | GROUP child => isCharMatch (child, pos, curChr) + + fun positionsThatCorrespondToChar + (char, curStates, regex, acc, followSet, hasAnyMatch) = + case curStates of + [] => List.concat (Set.valuesToList acc) + | pos :: tl => + if isCharMatch (regex, pos, Char.chr char) then + let + (* get union of new and previous follows *) + val prevFollows = Set.getOrDefault (char, acc, []) + val newFollows = Set.getOrDefault (pos, followSet, []) + + val tempSet = Set.addFromList (prevFollows, Set.LEAF) + val tempSet = Set.addFromList (newFollows, tempSet) + val allFollowList = Set.keysToList tempSet + + (* store union of new and previous follows so far *) + val acc = Set.insertOrReplace (char, allFollowList, acc) + in + positionsThatCorrespondToChar + (char, tl, regex, acc, followSet, true) + end + else + positionsThatCorrespondToChar + (char, tl, regex, acc, followSet, hasAnyMatch) + + structure Dtran = + struct + (* vector, with idx corresponding to state in dstate, + * an int key which corresponds to char's ascii code, + * and an int value corresponding to state we will transition to *) + type t = int Set.set vector + + fun insert (dStateIdx, char, toStateIdx, dtran: t) = + if dStateIdx = Vector.length dtran then + let + val el = Set.insertOrReplace (char, toStateIdx, Set.LEAF) + val el = Vector.fromList [el] + in + Vector.concat [dtran, el] + end + else if dStateIdx < Vector.length dtran then + let + val el = Vector.sub (dtran, dStateIdx) + val el = Set.insertOrReplace (char, toStateIdx, el) + in + Vector.update (dtran, dStateIdx, el) + end + else + let + val appendLength = dStateIdx - Vector.length dtran + val appendVecs = Vector.tabulate (appendLength, fn _ => Set.LEAF) + val dtran = Vector.concat [dtran, appendVecs] + in + insert (dStateIdx, char, toStateIdx, dtran) + end + end + + fun convertChar + ( char + , regex + , dstates + , dtran: Dtran.t + , unmarkedState + , unmarkedIdx + , followSet + , prevDstateLength + ) = + if char < 0 then + (dstates, dtran) + else + let + val u = positionsThatCorrespondToChar + (char, unmarkedState, regex, Set.LEAF, followSet, false) + in + case u of + [] => + convertChar + ( char - 1 + , regex + , dstates + , dtran + , unmarkedState + , unmarkedIdx + , followSet + , prevDstateLength + ) + | _ => + let + (* dtran is idx -> char -> state_list map *) + val (uIdx, dstates) = appendIfNew (0, dstates, u) + val dtran = Dtran.insert (unmarkedIdx, char, uIdx, dtran) + in + convertChar + ( char - 1 + , regex + , dstates + , dtran + , unmarkedState + , unmarkedIdx + , followSet + , prevDstateLength + ) + end + end + + fun convertLoop (regex, dstates, dtran, followSet) = + case getUnmarkedTransitionsIfExists (0, dstates) of + SOME (unmarkedIdx, unamarkedTransition) => + let + (* mark transition *) + val dstates = + let + val newMark = {marked = true, transitions = unamarkedTransition} + in + Vector.update (dstates, unmarkedIdx, newMark) + end + + val (dstates, dtran) = convertChar + ( 255 + , regex + , dstates + , dtran + , unamarkedTransition + , unmarkedIdx + , followSet + , Vector.length dstates + ) + in + convertLoop (regex, dstates, dtran, followSet) + end + | NONE => + Vector.map + (fn set => + Vector.tabulate (256, fn i => Set.getOrDefault (i, set, ~1))) + dtran + + fun convert regex = + let + val followSet = addToFollowSet (regex, Set.LEAF) + + (* get firstpos, sorted *) + val first = firstpos (regex, []) + val first = Set.addFromList (first, Set.LEAF) + val first = Set.keysToList first + + val dstates = Vector.fromList [{transitions = first, marked = false}] + in + convertLoop (regex, dstates, Vector.fromList [Set.LEAF], followSet) + end + end + + fun fromString str = + case ParseDfa.parse (str, 0) of + SOME (ast, numStates) => + let + val endMarker = + CHAR_LITERAL {char = Fn.endMarker, position = numStates + 1} + val ast = CONCAT + { l = ast + , leftMaxState = numStates + , r = endMarker + , rightMaxState = numStates + 1 + } + in + ToDfa.convert ast + end + | NONE => Vector.fromList [] + + type dfa = int vector vector + type dfa_state = int + + fun nextState (dfa: dfa, curState: dfa_state, chr) = + let val curTable = Vector.sub (dfa, curState) + in Vector.sub (curTable, Char.ord chr) + end + + fun isFinal (dfa: dfa, curState: dfa_state) = + curState <> ~1 + andalso + let + val curTable = Vector.sub (dfa, curState) + val endMarkerCode = Char.ord Fn.endMarker + in + Vector.sub (curTable, endMarkerCode) <> ~1 + end + + fun isDead (curState: dfa_state) = curState = ~1 + + fun helpMatchString (strPos, str, dfa, curState, startPos, prevFinalPos, acc) = + if strPos = String.size str then + let + val acc = + if prevFinalPos = ~1 then acc else (startPos, prevFinalPos) :: acc + in + List.rev acc + end + else + let + val chr = String.sub (str, strPos) + val newState = nextState (dfa, curState, chr) + val prevFinalPos = + if isFinal (dfa, newState) then strPos else prevFinalPos + in + if isDead newState then + if prevFinalPos = ~1 then + (* restart from startPos *) + helpMatchString (startPos + 1, str, dfa, 0, startPos + 1, ~1, acc) + else + let + val acc = (startPos, prevFinalPos) :: acc + in + helpMatchString + (prevFinalPos + 1, str, dfa, 0, prevFinalPos + 1, ~1, acc) + end + else + helpMatchString + (strPos + 1, str, dfa, newState, startPos, prevFinalPos, acc) + end + + fun matchString (dfa, string) = + if Vector.length dfa = 0 then [] + else helpMatchString (0, string, dfa, 0, 0, ~1, []) +end + +structure CaseInsensitiveDfa = + MakeDfaGen + (struct + val endMarker = #"\^@" + fun charIsEqual (a: char, b: char) = Char.toLower a = Char.toLower b + end) + +structure CaseSensitiveDfa = + MakeDfaGen + (struct + val endMarker = #"\^@" + fun charIsEqual (a: char, b: char) = a = b + end) diff --git a/shf/fcore/search-list/search-list.sml b/shf/fcore/search-list/search-list.sml new file mode 100644 index 0000000..e05f0bd --- /dev/null +++ b/shf/fcore/search-list/search-list.sml @@ -0,0 +1,254 @@ +structure SearchList = +struct + structure Dfa = CaseInsensitiveDfa + + fun buildLoop (idx, buffer, dfa, acc, curState, startPos, prevFinalPos) = + let + val buffer = LineGap.goToIdx (idx, buffer) + in + if idx = #textLength buffer then + let + val acc = + if prevFinalPos < 0 then acc + else PersistentVector.append (startPos, prevFinalPos, acc) + in + (buffer, acc) + end + else + let + val chr = LineGap.sub (idx, buffer) + val newState = Dfa.nextState (dfa, curState, chr) + val prevFinalPos = + if Dfa.isFinal (dfa, newState) then idx else prevFinalPos + in + if Dfa.isDead newState then + if prevFinalPos = ~1 then + (* no match found: restart search from `startPos + 1` *) + buildLoop (startPos + 1, buffer, dfa, acc, 0, startPos + 1, ~1) + else + (* match found: append and continue *) + let + val acc = PersistentVector.append (startPos, prevFinalPos, acc) + + (* we start 1 idx after the final position we found *) + val newStart = prevFinalPos + 1 + in + buildLoop (newStart, buffer, dfa, acc, 0, newStart, ~1) + end + else + buildLoop + (idx + 1, buffer, dfa, acc, newState, startPos, prevFinalPos) + end + end + + fun build (buffer, dfa) = + if Vector.length dfa > 0 then + let val buffer = LineGap.goToStart buffer + in buildLoop (0, buffer, dfa, PersistentVector.empty, 0, 0, ~1) + end + else + (buffer, PersistentVector.empty) + + fun rangeLoop + ( dfa + , bufferPos + , buffer + , finishIdx + , searchList + , curState + , startPos + , prevFinalPos + ) = + if bufferPos = #textLength buffer orelse bufferPos > finishIdx then + let + val searchList = + if prevFinalPos = ~1 then searchList + else PersistentVector.append (startPos, prevFinalPos, searchList) + in + (buffer, searchList) + end + else + let + val buffer = LineGap.goToIdx (bufferPos, buffer) + val chr = LineGap.sub (bufferPos, buffer) + val newState = Dfa.nextState (dfa, curState, chr) + val prevFinalPos = + if Dfa.isFinal (dfa, newState) then bufferPos else prevFinalPos + in + if Dfa.isDead newState then + if prevFinalPos = ~1 then + (* no match found: restart search from `startPos + 1` *) + rangeLoop + ( dfa + , startPos + 1 + , buffer + , finishIdx + , searchList + , 0 + , startPos + 1 + , ~1 + ) + else + (* match found: append and continue *) + let + val searchList = + PersistentVector.append (startPos, prevFinalPos, searchList) + + (* we start 1 idx after the final position we found *) + val newStart = prevFinalPos + 1 + in + rangeLoop + (dfa, newStart, buffer, finishIdx, searchList, 0, newStart, ~1) + end + else + (* continue searching for match *) + rangeLoop + ( dfa + , bufferPos + 1 + , buffer + , finishIdx + , searchList + , newState + , startPos + , prevFinalPos + ) + end + + fun buildRange (buffer, finishIdx, dfa) = + if Vector.length dfa > 0 then + rangeLoop + ( dfa + , #idx buffer + , buffer + , finishIdx + , PersistentVector.empty + , 0 + , #idx buffer + , ~1 + ) + else + (buffer, PersistentVector.empty) + + fun insertUntilMatch + (idx, buffer, searchList, dfa, curState, startPos, prevFinalPos) = + if idx = #textLength buffer then + if prevFinalPos < 0 then + (buffer, searchList) + else if PersistentVector.isInRange (prevFinalPos, searchList) then + (buffer, searchList) + else + let + val searchList = + PersistentVector.insertMatchKeepingAbsoluteInddices + (startPos, prevFinalPos, searchList) + in + (buffer, searchList) + end + else if Dfa.isDead curState then + if prevFinalPos = ~1 then + (* no match found: restart search from `startPos + 1` *) + insertUntilMatch + (startPos + 1, buffer, searchList, dfa, 0, startPos + 1, ~1) + else if PersistentVector.isInRange (prevFinalPos, searchList) then + (buffer, searchList) + else + (* new match. Insert and continue *) + let + val searchList = + PersistentVector.insertMatchKeepingAbsoluteInddices + (startPos, prevFinalPos, searchList) + val newStart = prevFinalPos + 1 + in + insertUntilMatch (newStart, buffer, searchList, dfa, 0, newStart, ~1) + end + else + let + val buffer = LineGap.goToIdx (idx, buffer) + val chr = LineGap.sub (idx, buffer) + val newState = Dfa.nextState (dfa, curState, chr) + val prevFinalPos = + if Dfa.isFinal (dfa, newState) then idx else prevFinalPos + in + (* continue *) + insertUntilMatch + (idx + 1, buffer, searchList, dfa, newState, startPos, prevFinalPos) + end + + fun tryExtendingPrevMatch + (idx, buffer, searchList, dfa, finalPos, curState, start) = + if idx = #textLength buffer then + let + val searchList = + PersistentVector.extendExistingMatch (start, finalPos, searchList) + in + (buffer, searchList) + end + else if Dfa.isDead curState then + let + val searchList = + PersistentVector.extendExistingMatch (start, finalPos, searchList) + in + insertUntilMatch + (finalPos + 1, buffer, searchList, dfa, 0, finalPos + 1, ~1) + end + else + let + val buffer = LineGap.goToIdx (idx, buffer) + val chr = LineGap.sub (idx, buffer) + val newState = Dfa.nextState (dfa, curState, chr) + val finalPos = if Dfa.isFinal (dfa, newState) then idx else finalPos + in + (* continue *) + tryExtendingPrevMatch + (idx + 1, buffer, searchList, dfa, finalPos, newState, start) + end + + fun deleteBufferAndSearchList (start, length, buffer, searchList, dfa) = + let + val buffer = LineGap.delete (start, length, buffer) + val searchList = PersistentVector.delete (start, length, searchList) + val oldStart = PersistentVector.prevMatch (start, searchList, 1) + in + if Vector.length dfa = 0 then + (buffer, searchList) + else if oldStart >= start orelse oldStart = ~1 then + (* no previous match, so try searching for a match from start of buffer *) + insertUntilMatch (0, buffer, searchList, dfa, 0, 0, ~1) + else + tryExtendingPrevMatch + (oldStart, buffer, searchList, dfa, ~1, 0, oldStart) + end + + (* inserts into buffer and searchList both *) + fun insert (insIdx, insString, buffer, searchList, dfa) = + let + val buffer = LineGap.insert (insIdx, insString, buffer) + + (* incremennt existing elements in the searchList after the insIdx + * by the length of the string that was just inserted *) + val searchList = + let + val searchListLeft = PersistentVector.splitLeft (insIdx, searchList) + + val insLength = String.size insString + val searchListRight = + PersistentVector.splitRight (insIdx + insLength, searchList) + val searchListRight = PersistentVector.empty + in + if PersistentVector.isEmpty searchListLeft then searchListRight + else if PersistentVector.isEmpty searchListRight then searchListLeft + else PersistentVector.merge (searchListLeft, searchListRight) + end + + val oldStart = PersistentVector.prevMatch (insIdx, searchList, 1) + in + if Vector.length dfa = 0 then + (buffer, searchList) + else if oldStart >= insIdx orelse oldStart = ~1 then + (* no previous match, so try searching for a match from start of buffer *) + insertUntilMatch (0, buffer, searchList, dfa, 0, 0, ~1) + else + tryExtendingPrevMatch + (oldStart, buffer, searchList, dfa, ~1, 0, oldStart) + end +end diff --git a/shf/fcore/text-builder/normal-mode-text-builder.sml b/shf/fcore/text-builder/normal-mode-text-builder.sml new file mode 100644 index 0000000..0a1d59e --- /dev/null +++ b/shf/fcore/text-builder/normal-mode-text-builder.sml @@ -0,0 +1,142 @@ +structure NormalModeTextBuilder = +struct + structure Utils = TextBuilderUtils + + (* Prerequisite to all functions in this structure: + * - Move buffer to startLine before calling any function. *) + + fun startBuild + ( startLine + , cursorPos + , buffer: LineGap.t + , windowWidth + , windowHeight + , floatWindowWidth + , floatWindowHeight + , searchList + , visualScrollColumn + , acc + ) = + let + val + { rightStrings + , rightLines + , line = curLine + , idx = curIdx + , textLength + , ... + } = buffer + + val env = Utils.initEnv + ( 0 + , 0 + , windowWidth + , windowHeight + , floatWindowWidth + , floatWindowHeight + , searchList + , visualScrollColumn + , startLine + ) + val {startX, startY, ...} = env + in + if textLength = 1 then + (* empty string, so there is nothing we can draw + * except a cursor at the line start. + * An empty string is usually thought of to have a length of 0 + * and this is true, but we always have a \n at the end of the buffer + * to respect Unix-style file endings, which we always uphold. + * So, for us, an empty string has a length of 1. *) + [Utils.makeCursor (startX, startY, env)] + else + case (rightStrings, rightLines) of + (shd :: stl, lhd :: ltl) => + let + (* get relative index of line to start building from *) + val strPos = + Utils.getRelativeLineStartFromRightHead + (startLine, curLine, lhd) + (* get absolute idx of line *) + val absIdx = curIdx + strPos + in + if PersistentVector.isEmpty searchList then + TextBuilderWithCursor.build + ( strPos + , shd + , stl + , lhd + , ltl + , startX + , startY + , 0 + , startLine + , absIdx + , cursorPos + , env + , acc + ) + else + TextBuilderWithHighlight.build + ( strPos + , shd + , stl + , lhd + , ltl + , startX + , startY + , 0 + , startLine + , absIdx + , cursorPos + , env + , acc + ) + end + | (_, _) => acc + end + + fun buildWithExisting + ( startLine + , cursorPos + , buffer: LineGap.t + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + , acc + ) = + startBuild + ( startLine + , cursorPos + , buffer + , windowWidth + , windowHeight + , Real32.fromInt windowWidth + , Real32.fromInt windowHeight + , searchList + , visualScrollColumn + , [] + ) + + fun build + ( startLine + , cursorPos + , buffer: LineGap.t + , windowWidth + , windowHeight + , searchList + , visualScrollColumn + ) = + startBuild + ( startLine + , cursorPos + , buffer + , windowWidth + , windowHeight + , Real32.fromInt windowWidth + , Real32.fromInt windowHeight + , searchList + , visualScrollColumn + , [] + ) +end diff --git a/shf/fcore/text-builder/search-bar.sml b/shf/fcore/text-builder/search-bar.sml new file mode 100644 index 0000000..46e426f --- /dev/null +++ b/shf/fcore/text-builder/search-bar.sml @@ -0,0 +1,131 @@ +structure SearchBar = +struct + structure TC = TextConstants + structure Utils = TextBuilderUtils + + fun loop + (pos, str, posX, posY, endX, acc, floatWindowWidth, floatWindowHeight) = + if pos = String.size str then + acc + else if posX >= endX then + acc + else + let + val chr = String.sub (str, pos) + val r: Real32.real = 0.01 + val g: Real32.real = 0.01 + val b: Real32.real = 0.01 + val fPosX = Real32.fromInt posX + val fPosY = Real32.fromInt posY + val z: Real32.real = 0.1 + + val chr = CozetteAscii.make + ( chr + , fPosX + , fPosY + , z + , TC.scale + , floatWindowWidth + , floatWindowHeight + , r + , g + , b + ) + + val acc = chr :: acc + val nextPosX = posX + TC.xSpace + in + loop + ( pos + 1 + , str + , nextPosX + , posY + , endX + , acc + , floatWindowWidth + , floatWindowHeight + ) + end + + (* builds a single text line from a string. + * Used for getting Real32.real vector representing search input. + * Todo: add scrolling, so that text scrolls horizontally when greater than width. *) + fun build + ( str + , startX + , startY + , endX + , floatWindowWidth + , floatWindowHeight + , searchCursorIdx + , searchScrollColumn + , caseSensitive + ) = + let + val r: Real32.real = 0.1 + val g: Real32.real = 0.1 + val b: Real32.real = 0.1 + val z: Real32.real = 0.1 + + val width = endX - startX + val (startX, endX) = + if TC.textLineWidth > width then + (startX, endX) + else + let + val startX = (width - TC.textLineWidth) div 2 + val endX = startX + TC.textLineWidth + in + (startX, endX) + end + + val fPosX = Real32.fromInt startX + val fPosY = Real32.fromInt startY + + val searchSymbol = CozetteAscii.make + ( if caseSensitive then #"?" else #"/" + , fPosX + , fPosY + , z + , TC.scale + , floatWindowWidth + , floatWindowHeight + , r + , g + , b + ) + + val cursor = + let + val xpos = (searchCursorIdx + 1) - searchScrollColumn + val xpos = TextConstants.xSpace * xpos + startX + val xpos = Int.min (endX, xpos) + val x = Real32.fromInt xpos + in + PipeCursor.lerp + ( x + , fPosY + , 0.01 + , TC.scale + , floatWindowWidth + , floatWindowHeight + , r + , g + , b + ) + end + + val posX = startX + TC.xSpace + in + loop + ( searchScrollColumn + , str + , posX + , startY + , endX + , [cursor, searchSymbol] + , floatWindowWidth + , floatWindowHeight + ) + end +end diff --git a/shf/fcore/text-builder/text-builder-utils.sml b/shf/fcore/text-builder/text-builder-utils.sml new file mode 100644 index 0000000..0cc1cdb --- /dev/null +++ b/shf/fcore/text-builder/text-builder-utils.sml @@ -0,0 +1,250 @@ +structure TextBuilderUtils = +struct + structure TC = TextConstants + + type env_data = + { charR: Real32.real + , charG: Real32.real + , charB: Real32.real + + , cursorR: Real32.real + , cursorG: Real32.real + , cursorB: Real32.real + + , searchHighlightR: Real32.real + , searchHighlightG: Real32.real + , searchHighlightB: Real32.real + + (* different colours for char when cursor is on char *) + , cursorHighlightedCharR: Real32.real + , cursorHighlightedCharG: Real32.real + , cursorHighlightedCharB: Real32.real + + , searchHighlightedCharR: Real32.real + , searchHighlightedCharG: Real32.real + , searchHighlightedCharB: Real32.real + + , charZ: Real32.real + , cursorZ: Real32.real + , searchHighlightZ: Real32.real + + , startX: int + , startY: int + + , scrollColumnStart: int + , scrollColumnEnd: int + , lastLineNumber: int + + (* fw/fh = float window width and float window height *) + , fw: Real32.real + , fh: Real32.real + + , searchList: PersistentVector.t + } + + fun initEnv + ( startX + , startY + , endX + , endY + , floatWindowWidth + , floatWindowHeight + , searchList + , visualScrollColumn + , startLine + ) : env_data = + let + val width = endX - startX + val lastLineNumber = + let + val height = endY - startY + val howManyLines = height div TC.ySpace + in + startLine + howManyLines + end + in + if TC.textLineWidth > width then + { charR = 0.0 + , charG = 0.0 + , charB = 0.0 + + , searchHighlightR = 0.41 + , searchHighlightG = 0.05 + , searchHighlightB = 0.67 + + , cursorR = 0.0 + , cursorG = 0.0 + , cursorB = 0.0 + + , searchHighlightedCharR = 0.89 + , searchHighlightedCharG = 0.89 + , searchHighlightedCharB = 0.89 + + , cursorHighlightedCharR = 0.89 + , cursorHighlightedCharG = 0.89 + , cursorHighlightedCharB = 0.89 + + , charZ = 0.01 + , cursorZ = 0.03 + , searchHighlightZ = 0.05 + + , startX = startX + , startY = startX + + , scrollColumnStart = visualScrollColumn + , scrollColumnEnd = width div TC.xSpace + visualScrollColumn + , lastLineNumber = lastLineNumber + + , fw = floatWindowWidth + , fh = floatWindowHeight + + , searchList = searchList + } + else + let + val startX = (width - TC.textLineWidth) div 2 + in + { charR = 0.0 + , charG = 0.0 + , charB = 0.0 + + , searchHighlightR = 0.41 + , searchHighlightG = 0.05 + , searchHighlightB = 0.67 + + , cursorR = 0.0 + , cursorG = 0.0 + , cursorB = 0.0 + + , searchHighlightedCharR = 0.89 + , searchHighlightedCharG = 0.89 + , searchHighlightedCharB = 0.89 + + , cursorHighlightedCharR = 0.89 + , cursorHighlightedCharG = 0.89 + , cursorHighlightedCharB = 0.89 + + , charZ = 0.01 + , cursorZ = 0.03 + , searchHighlightZ = 0.05 + + , startX = startX + , startY = startY + + , scrollColumnStart = visualScrollColumn + , scrollColumnEnd = visualScrollColumn + TC.textLineCount + , lastLineNumber = lastLineNumber + + , fw = floatWindowWidth + , fh = floatWindowHeight + + , searchList = searchList + } + end + end + + (* different functions to make vectors of different things we want to draw. *) + fun makeCursor (posX, posY, env: env_data) = + Rect.lerp + ( Real32.fromInt (posX - 2) + , Real32.fromInt posY + , #cursorZ env + , TC.scale + , #fw env + , #fh env + , #cursorR env + , #cursorG env + , #cursorB env + ) + + fun makeSearchHighlight (posX, posY, env: env_data) = + Rect.lerp + ( Real32.fromInt (posX - 2) + , Real32.fromInt posY + , #searchHighlightZ env + , TC.scale + , #fw env + , #fh env + , #searchHighlightR env + , #searchHighlightG env + , #searchHighlightB env + ) + + fun makeChr (chr, posX, posY, env: env_data) = + CozetteAscii.make + ( chr + , Real32.fromInt posX + , Real32.fromInt posY + , #charZ env + , TC.scale + , #fw env + , #fh env + , #charR env + , #charG env + , #charB env + ) + + fun makeCursorHighlightedChr (chr, posX, posY, env: env_data) = + CozetteAscii.make + ( chr + , Real32.fromInt posX + , Real32.fromInt posY + , #charZ env + , TC.scale + , #fw env + , #fh env + , #cursorHighlightedCharR env + , #cursorHighlightedCharG env + , #cursorHighlightedCharB env + ) + + fun makeSearchHighlightedChr (chr, posX, posY, env: env_data) = + CozetteAscii.make + ( chr + , Real32.fromInt posX + , Real32.fromInt posY + , #charZ env + , TC.scale + , #fw env + , #fh env + , #searchHighlightedCharR env + , #searchHighlightedCharG env + , #searchHighlightedCharB env + ) + + (* gets line start idx, relative to right hd *) + fun getRelativeLineStartFromRightHead (startLine, curLine, rLnHd) = + if startLine > curLine then + let val lnPos = startLine - curLine - 1 + in Vector.sub (rLnHd, lnPos) + 1 + end + else + 0 + + (* gets line start idx, absolute *) + fun getAbsoluteLineStartFromRightHead (curIdx, startLine, curLine, rLnHd) = + let + val startIdx = + if startLine > curLine then + let val lnPos = startLine - curLine - 1 + in Vector.sub (rLnHd, lnPos) + 1 + end + else + 0 + in + curIdx + startIdx + end + + fun getLineAbsIdxFromBuffer (startLine, buffer: LineGap.t) = + let + val {rightLines, line = curLine, idx = curIdx, ...} = buffer + in + case rightLines of + rLnHd :: _ => + getAbsoluteLineStartFromRightHead (curIdx, startLine, curLine, rLnHd) + | [] => + raise Fail + "text-builder-utils.sml 268:\ + \should never call function when at end of buffer" + end +end diff --git a/shf/fcore/text-builder/text-builder-with-cursor.sml b/shf/fcore/text-builder/text-builder-with-cursor.sml new file mode 100644 index 0000000..fb6b56e --- /dev/null +++ b/shf/fcore/text-builder/text-builder-with-cursor.sml @@ -0,0 +1,253 @@ +structure TextBuilderWithCursor = +struct + structure TC = TextConstants + structure Utils = TextBuilderUtils + + fun goToFirstLineAfter + (stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) = + case (stl, ltl) of + (shd :: stl, lhd :: ltl) => + if Vector.length lhd > 0 then + let + val lineOffset = Vector.sub (lhd, 0) + val strPos = lineOffset + 1 + val absIdx = absIdx + strPos + val posY = posY + TC.ySpace + val lineNumber = lineNumber + 1 + in + build + ( strPos + , shd + , stl + , lhd + , ltl + , #startX env + , posY + , 0 + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + end + else + (* keep looping until we find a linebreak *) + goToFirstLineAfter + ( stl + , ltl + , posY + , lineNumber + , absIdx + String.size shd + , cursorIdx + , env + , acc + ) + | (_, _) => acc + + and skipToNextLine + (pos, str, stl, line, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) = + if Vector.length line = 0 then + let + (* get index of buffer after this string *) + val absIdx = absIdx - pos + val absIdx = absIdx + String.size str + in + goToFirstLineAfter + (stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) + end + else + (* bin search lines *) + let + val linePos = BinSearch.equalOrMore (pos + 1, line) + in + if linePos = ~1 then + (* next line is not in this node *) + let + val absIdx = absIdx - pos + val absIdx = absIdx + String.size str + in + goToFirstLineAfter + (stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) + end + else + let + val lineOffset = Vector.sub (line, linePos) + val newStrPos = lineOffset + 1 + val absIdx = absIdx - pos + newStrPos + val posY = posY + TC.ySpace + val lineNumber = lineNumber + 1 + in + build + ( newStrPos + , str + , stl + , line + , ltl + , #startX env + , posY + , 0 + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + end + end + + and build + ( pos + , str + , stl + , line + , ltl + , posX + , posY + , column + , lineNumber + , absIdx + , cursorIdx + , env: Utils.env_data + , acc + ) = + if pos = String.size str then + case (stl, ltl) of + (str :: stl, line :: ltl) => + build + ( 0 + , str + , stl + , line + , ltl + , posX + , posY + , column + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + | (_, _) => acc + else + case String.sub (str, pos) of + #"\n" => + if lineNumber + 1 > #lastLineNumber env then + acc + else + let + val acc = + if absIdx = cursorIdx then + Utils.makeCursor (posX, posY, env) :: acc + else + acc + in + build + ( pos + 1 + , str + , stl + , line + , ltl + , #startX env + , posY + TC.ySpace + , 0 + , lineNumber + 1 + , absIdx + 1 + , cursorIdx + , env + , acc + ) + end + | #" " => + let + val acc = + if absIdx = cursorIdx then + Utils.makeCursor (posX, posY, env) :: acc + else + acc + val posX = + if column < #scrollColumnStart env then + (* if we are prior to the start column, + * we want to set the x position to be at the start + * in preparation for when we are at the start column *) + #startX env + else + posX + TC.xSpace + in + build + ( pos + 1 + , str + , stl + , line + , ltl + , posX + , posY + , column + 1 + , lineNumber + , absIdx + 1 + , cursorIdx + , env + , acc + ) + end + | chr => + if column < #scrollColumnStart env then + build + ( pos + 1 + , str + , stl + , line + , ltl + , #startX env + , posY + , column + 1 + , lineNumber + , absIdx + 1 + , cursorIdx + , env + , acc + ) + else if column > #scrollColumnEnd env then + skipToNextLine + ( pos + , str + , stl + , line + , ltl + , posY + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + else + let + val acc = + if absIdx = cursorIdx then + let + val acc = Utils.makeCursor (posX, posY, env) :: acc + in + Utils.makeCursorHighlightedChr (chr, posX, posY, env) :: acc + end + else + Utils.makeChr (chr, posX, posY, env) :: acc + in + build + ( pos + 1 + , str + , stl + , line + , ltl + , posX + TC.xSpace + , posY + , column + 1 + , lineNumber + , absIdx + 1 + , cursorIdx + , env + , acc + ) + end +end diff --git a/shf/fcore/text-builder/text-builder-with-highlight.sml b/shf/fcore/text-builder/text-builder-with-highlight.sml new file mode 100644 index 0000000..84c6e88 --- /dev/null +++ b/shf/fcore/text-builder/text-builder-with-highlight.sml @@ -0,0 +1,258 @@ +structure TextBuilderWithHighlight = +struct + structure TC = TextConstants + structure Utils = TextBuilderUtils + + fun isSecondLastChr (pos, str, tl) = + case tl of + [] => pos = String.size str - 2 + | _ => false + + fun goToFirstLineAfter + (stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) = + case (stl, ltl) of + (shd :: stl, lhd :: ltl) => + if Vector.length lhd > 0 then + let + val lineOffset = Vector.sub (lhd, 0) + val strPos = lineOffset + 1 + val absIdx = absIdx + strPos + val posY = posY + TC.ySpace + val lineNumber = lineNumber + 1 + in + build + ( strPos + , shd + , stl + , lhd + , ltl + , #startX env + , posY + , 0 + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + end + else + (* keep looping until we find a linebreak *) + goToFirstLineAfter + ( stl + , ltl + , posY + , lineNumber + , absIdx + String.size shd + , cursorIdx + , env + , acc + ) + | (_, _) => acc + + and skipToNextLine + (pos, str, stl, line, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) = + if Vector.length line = 0 then + let + (* get index of buffer after this string *) + val absIdx = absIdx - pos + val absIdx = absIdx + String.size str + in + goToFirstLineAfter + (stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) + end + else + (* bin search lines *) + let + val linePos = BinSearch.equalOrMore (pos + 1, line) + in + if linePos = ~1 then + (* next line is not in this node *) + let + val absIdx = absIdx - pos + val absIdx = absIdx + String.size str + in + goToFirstLineAfter + (stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) + end + else + let + val lineOffset = Vector.sub (line, linePos) + val newStrPos = lineOffset + 1 + val absIdx = absIdx - pos + newStrPos + val posY = posY + TC.ySpace + val lineNumber = lineNumber + 1 + in + build + ( newStrPos + , str + , stl + , line + , ltl + , #startX env + , posY + , 0 + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + end + end + + and build + ( pos + , str + , stl + , line + , ltl + , posX + , posY + , column + , lineNumber + , absIdx + , cursorIdx + , env: Utils.env_data + , acc + ) = + if pos = String.size str then + case (stl, ltl) of + (str :: stl, line :: ltl) => + build + ( 0 + , str + , stl + , line + , ltl + , posX + , posY + , column + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + | (_, _) => acc + else + case String.sub (str, pos) of + #" " => + let + val acc = + if absIdx = cursorIdx then + Utils.makeCursor (posX, posY, env) :: acc + else + acc + val acc = + if PersistentVector.isInRange (absIdx, #searchList env) then + Utils.makeSearchHighlight (posX, posY, env) :: acc + else + acc + val posX = + if column < #scrollColumnStart env then #startX env + else posX + TC.xSpace + in + build + ( pos + 1 + , str + , stl + , line + , ltl + , posX + , posY + , column + 1 + , lineNumber + , absIdx + 1 + , cursorIdx + , env + , acc + ) + end + | #"\n" => + if lineNumber + 1 > #lastLineNumber env then + acc + else + let + val acc = + if absIdx = cursorIdx then + Utils.makeCursor (posX, posY, env) :: acc + else + acc + in + build + ( pos + 1 + , str + , stl + , line + , ltl + , #startX env + , posY + TC.ySpace + , 0 + , lineNumber + 1 + , absIdx + 1 + , cursorIdx + , env + , acc + ) + end + | chr => + if column < #scrollColumnStart env then + build + ( pos + 1 + , str + , stl + , line + , ltl + , #startX env + , posY + , column + 1 + , lineNumber + , absIdx + 1 + , cursorIdx + , env + , acc + ) + else if column > #scrollColumnEnd env then + skipToNextLine + ( pos + , str + , stl + , line + , ltl + , posY + , lineNumber + , absIdx + , cursorIdx + , env + , acc + ) + else + let + val acc = + if absIdx = cursorIdx then + Utils.makeCursorHighlightedChr (chr, posX, posY, env) + :: Utils.makeCursor (posX, posY, env) :: acc + else if PersistentVector.isInRange (absIdx, #searchList env) then + Utils.makeSearchHighlightedChr (chr, posX, posY, env) + :: Utils.makeSearchHighlight (posX, posY, env) :: acc + else + Utils.makeChr (chr, posX, posY, env) :: acc + in + build + ( pos + 1 + , str + , stl + , line + , ltl + , posX + TC.xSpace + , posY + , column + 1 + , lineNumber + , absIdx + 1 + , cursorIdx + , env + , acc + ) + end +end diff --git a/shf/fcore/text-constants.sml b/shf/fcore/text-constants.sml new file mode 100644 index 0000000..63d333c --- /dev/null +++ b/shf/fcore/text-constants.sml @@ -0,0 +1,10 @@ +structure TextConstants = +struct + val xSpace = 13 + val xSpace3 = xSpace * 3 + val ySpace = 25 + val scale: Real32.real = 2.0 + + val textLineCount = 80 + val textLineWidth = xSpace * textLineCount +end diff --git a/shf/fcore/text-scroll.sml b/shf/fcore/text-scroll.sml new file mode 100644 index 0000000..11f2978 --- /dev/null +++ b/shf/fcore/text-scroll.sml @@ -0,0 +1,68 @@ +structure TextScroll = +struct + structure TC = TextConstants + + (* calculates new scroll column from integer arguments *) + fun calculateScrollColumn + (startOfLine, cursorIdx, windowWidth, prevScrollColumn) = + let + val newColumn = cursorIdx - startOfLine + val howManyColumnsCanWeFit = + if windowWidth >= TC.textLineWidth then TC.textLineCount + else windowWidth div TC.xSpace + val howManyColumnsCanWeFit = howManyColumnsCanWeFit - 1 + in + if newColumn < prevScrollColumn then + (* we are moving the cursor backwards + * so make sure that newColumn is on the left side *) + newColumn + else if newColumn > prevScrollColumn + howManyColumnsCanWeFit then + (* we are scrolling forwards *) + newColumn - howManyColumnsCanWeFit + else + (* we can display the current column without moving the scroll column + * so we do that *) + prevScrollColumn + end + + (* Preqreuisite: move buffer to cursorIdx *) + fun getScrollColumn (buffer, cursorIdx, windowWidth, prevScrollColumn) = + let + val startOfLine = Cursor.vi0 (buffer, cursorIdx) + in + calculateScrollColumn + (startOfLine, cursorIdx, windowWidth, prevScrollColumn) + end + + fun getScrollColumnFromString (cursorIdx, windowWidth, prevScrollColumn) = + calculateScrollColumn (0, cursorIdx, windowWidth, prevScrollColumn) + + fun getStartLine (prevLineNumber, cursorLine, windowHeight, totalLines) = + if cursorLine <= (prevLineNumber + 3) then + (* cursorLine is prior to or same as prevLineNumber, + * so use cursorLine to calculate the start line we want. *) + Int.max (cursorLine - 3, 0) + else + (* cursorLine > prevLineNumber *) + let + val howManyLinesWeCanFit = windowHeight div TC.ySpace + in + if cursorLine > prevLineNumber + (howManyLinesWeCanFit - 3) then + (* cursorLine is after the visible part of the screen + * so return the mimimum line where cursorLine is visible *) + if cursorLine >= totalLines - 3 then + Int.max (totalLines - howManyLinesWeCanFit, 0) + else + cursorLine - howManyLinesWeCanFit + 3 + else + prevLineNumber + end + + fun getLineCentre (cursorLine, windowHeight) = + let + val howManyLinesWeCanFit = windowHeight div TC.ySpace + val startLine = cursorLine - (howManyLinesWeCanFit div 2) + in + Int.max (startLine, 0) + end +end diff --git a/shf/ffi/RGFW.h b/shf/ffi/RGFW.h new file mode 100644 index 0000000..2a7c19f --- /dev/null +++ b/shf/ffi/RGFW.h @@ -0,0 +1,15217 @@ +/* +* +* RGFW 2.0.0-dev + +* Copyright (C) 2022-26 Riley Mabb (@ColleagueRiley) +* +* libpng license +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. + +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +* +* +*/ + +/* + (MAKE SURE RGFW_IMPLEMENTATION is in exactly one header or you use -D RGFW_IMPLEMENTATION) + #define RGFW_IMPLEMENTATION - makes it so source code is included with header +*/ + +/* + #define RGFW_IMPLEMENTATION - (required) makes it so the source code is included + #define RGFW_DEBUG - (optional) makes it so RGFW prints debug messages and errors when they're found + #define RGFW_EGL - (optional) compile with OpenGL functions, allowing you to use to use EGL instead of the native OpenGL functions + #define RGFW_DIRECTX - (optional) include integration directX functions (windows only) + #define RGFW_VULKAN - (optional) include helpful vulkan integration functions and macros + #define RGFW_WEBGPU - (optional) use WebGPU for rendering + #define RGFW_NATIVE - (optional) define native RGFW types that use native API structures + + #define RGFW_X11 (optional) (unix only) if X11 should be used. This option is turned on by default by unix systems except for MacOS + #define RGFW_WAYLAND (optional) (unix only) use Wayland. (This can be used with X11) + #define RGFW_NO_X11 (optional) (unix only) don't fallback to X11 when using Wayland + #define RGFW_NO_LOAD_WGL (optional) (windows only) if WGL should be loaded dynamically during runtime + #define RGFW_NO_X11_CURSOR (optional) (unix only) don't use XCursor + #define RGFW_NO_X11_CURSOR_PRELOAD (optional) (unix only) use XCursor, but don't link it in code, (you'll have to link it with -lXcursor) + #define RGFW_NO_X11_EXT_PRELOAD (optional) (unix only) use Xext, but don't link it in code, (you'll have to link it with -lXext) + #define RGFW_NO_LOAD_WINMM (optional) (windows only) use winmm (timeBeginPeriod), but don't link it in code, (you'll have to link it with -lwinmm) + #define RGFW_NO_WINMM (optional) (windows only) don't use winmm + #define RGFW_NO_IOKIT (optional) (macOS) don't use IOKit + #define RGFW_NO_UNIX_CLOCK (optional) (unix) don't link unix clock functions + #define RGFW_NO_DWM (windows only) - do not use or link dwmapi + #define RGFW_USE_XDL (optional) (X11) if XDL (XLib Dynamic Loader) should be used to load X11 dynamically during runtime (must include XDL.h along with RGFW) + #define RGFW_COCOA_GRAPHICS_SWITCHING - (optional) (cocoa) use automatic graphics switching (allow the system to choose to use GPU or iGPU) + #define RGFW_COCOA_FRAME_NAME (optional) (cocoa) set frame name + #define RGFW_NO_DPI - do not calculate DPI and don't use libShcore (win32) + #define RGFW_NO_XRANDR - do use XRandr (X11) + #define RGFW_ADVANCED_SMOOTH_RESIZE - use advanced methods for smooth resizing (may result in a spike in memory usage or worse performance) (eg. WM_TIMER and XSyncValue) + #define RGFW_NO_INFO - do not define the RGFW_info struct (without RGFW_IMPLEMENTATION) + #define RGFW_NO_GLXWINDOW - do not use GLXWindow + + #define RGFW_ALLOC x - choose the default allocation function (defaults to standard malloc) + #define RGFW_FREE x - choose the default deallocation function (defaults to standard free) + #define RGFW_USERPTR x - choose the default userptr sent to the malloc call, (NULL by default) + + #define RGFW_EXPORT - use when building RGFW + #define RGFW_IMPORT - use when linking with RGFW (not as a single-header) + + #define RGFW_USE_INT - force the use c-types rather than stdint.h (for systems that might not have stdint.h (msvc)) + #define RGFW_bool x - choose what type to use for bool, by default u32 is used +*/ + +/* +Example to get you started : + +linux : gcc main.c -lX11 -lXrandr -lGL +windows : gcc main.c -lopengl32 -lgdi32 +macos : gcc main.c -framework Cocoa -framework CoreVideo -framework OpenGL -framework IOKit + +#define RGFW_IMPLEMENTATION +#include "RGFW.h" + +u8 icon[4 * 3 * 3] = {0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF}; + +int main() { + RGFW_window* win = RGFW_createWindow("name", 100, 100, 500, 500, (u64)0); + RGFW_event event; + + RGFW_window_setExitKey(win, RGFW_escape); + RGFW_window_setIcon(win, icon, 3, 3, RGFW_formatRGBA8); + + while (RGFW_window_shouldClose(win) == RGFW_FALSE) { + while (RGFW_window_checkEvent(win, &event)) { + if (event.type == RGFW_quit) + break; + } + } + + RGFW_window_close(win); +} + + compiling : + + if you wish to compile the library all you have to do is create a new file with this in it + + rgfw.c + #define RGFW_IMPLEMENTATION + #include "RGFW.h" + + You may also want to add + `#define RGFW_EXPORT` when compiling and + `#define RGFW_IMPORT`when linking RGFW on it's own: + this reduces inline functions and prevents bloat in the object file + + then you can use gcc (or whatever compile you wish to use) to compile the library into object file + + ex. gcc -c RGFW.c -fPIC + + after you compile the library into an object file, you can also turn the object file into an static or shared library + + (commands ar and gcc can be replaced with whatever equivalent your system uses) + + static : ar rcs RGFW.a RGFW.o + shared : + windows: + gcc -shared RGFW.o -lopengl32 -lgdi32 -o RGFW.dll + linux: + gcc -shared RGFW.o -lX11 -lGL -lXrandr -o RGFW.so + macos: + gcc -shared RGFW.o -framework CoreVideo -framework Cocoa -framework OpenGL -framework IOKit +*/ + + + +/* + Credits : + EimaMei/Sacode : Code review, helped with X11, MacOS and Windows support, Silicon, siliapp.h -> referencing + + stb : This project is heavily inspired by the stb single header files + + SDL, GLFW and other online resources : reference implementations + + contributors : (feel free to put yourself here if you contribute) + krisvers (@krisvers) -> code review + EimaMei (@SaCode) -> code review + Nycticebus (@Code-Nycticebus) -> bug fixes + Rob Rohan (@robrohan) -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs + AICDG (@THISISAGOODNAME) -> vulkan support (example) + @Easymode -> support, testing/debugging, bug fixes and reviews + Joshua Rowe (omnisci3nce) - bug fix, review (macOS) + @lesleyrs -> bug fix, review (OpenGL) + Nick Porcino (@meshula) - testing, organization, review (MacOS, examples) + @therealmarrakesh -> documentation + @DarekParodia -> code review (X11) (C++) + @NishiOwO -> fix BSD support, fix OSMesa example + @BaynariKattu -> code review and documentation + Miguel Pinto (@konopimi) -> code review, fix vulkan example + @m-doescode -> code review (wayland) + Robert Gonzalez (@uni-dos) -> code review (wayland) + @TheLastVoyager -> code review + @yehoravramenko -> code review (winapi) + @halocupcake -> code review (OpenGL) + @GideonSerf -> documentation + Alexandre Almeida (@M374LX) -> code review (keycodes) + Vũ Xuân Trường (@wanwanvxt) -> code review (winapi) + Lucas (@lightspeedlucas) -> code review (msvc++) + Jeffery Myers (@JeffM2501) -> code review (msvc) + Zeni (@zenitsuyo) -> documentation + TheYahton (@TheYahton) -> documentation + nonexistant_object (@DiarrheaMcgee) + AC Gaudette (@acgaudette) +*/ + +#if _MSC_VER + #pragma comment(lib, "gdi32") + #pragma comment(lib, "shell32") + #pragma comment(lib, "User32") + #pragma comment(lib, "Advapi32") + #pragma warning( push ) + #pragma warning( disable : 4996 4191 4127) + #if _MSC_VER < 600 + #define RGFW_C89 + #endif +#else + #if defined(__STDC__) && !defined(__STDC_VERSION__) + #define RGFW_C89 + #endif +#endif + +#if defined(RGFW_EGL) && !defined(RGFW_OPENGL) + #define RGFW_OPENGL +#endif + +/* these OS macros look better & are standardized */ +/* plus it helps with cross-compiling */ + +#ifdef __EMSCRIPTEN__ + #define RGFW_WASM +#endif + +#if defined(RGFW_X11) && defined(__APPLE__) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_UNIX +#endif + +#if defined(_WIN32) && !defined(RGFW_X11) && !defined(RGFW_UNIX) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) /* (if you're using X11 on windows some how) */ + #define RGFW_WINDOWS +#endif +#if defined(RGFW_WAYLAND) + #define RGFW_DEBUG /* wayland will be in debug mode by default for now */ + #define RGFW_UNIX + #ifdef RGFW_OPENGL + #define RGFW_EGL + #endif + #ifdef RGFW_X11 + #define RGFW_DYNAMIC + #endif +#endif +#if (!defined(RGFW_WAYLAND) && !defined(RGFW_X11)) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_X11 + #define RGFW_UNIX +#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS +#endif + +#ifndef RGFW_ASSERT + #include + #define RGFW_ASSERT assert +#endif + +#if !defined(__STDC_VERSION__) + #define RGFW_C89 +#endif + +#if !defined(RGFW_SNPRINTF) && (defined(RGFW_X11) || defined(RGFW_WAYLAND)) + + /* required for X11 errors */ + #include + + #ifdef RGFW_C89 + #include + static int RGFW_c89_snprintf(char *dst, size_t size, const char *format, ...) { + va_list args; + size_t count = 0; + va_start(args, format); + count = (size_t)vsprintf(dst, format, args); + RGFW_ASSERT(count + 1 < size && "Buffer overflow"); + va_end(args); + return (int)count; + } + #define RGFW_SNPRINTF RGFW_c89_snprintf + #else + #define RGFW_SNPRINTF snprintf + #endif /*RGFW_C89*/ +#endif + +#ifndef RGFW_USERPTR + #define RGFW_USERPTR NULL +#endif + +#ifndef RGFW_UNUSED + #define RGFW_UNUSED(x) (void)(x) +#endif + +#ifndef RGFW_ROUND + #define RGFW_ROUND(x) (i32)((x) >= 0 ? (x) + 0.5f : (x) - 0.5f) +#endif + +#ifndef RGFW_ROUNDF + #define RGFW_ROUNDF(x) (float)((i32)((x) + ((x) < 0.0f ? -0.5f : 0.5f))) +#endif + +#ifndef RGFW_MIN + #define RGFW_MIN(x, y) ((x < y) ? x : y) +#endif + +#ifndef RGFW_ALLOC + #include + #define RGFW_ALLOC malloc + #define RGFW_FREE free +#endif + +#if !defined(RGFW_MEMCPY) || !defined(RGFW_STRNCMP) || !defined(RGFW_STRNCPY) || !defined(RGFW_MEMSET) + #include +#endif + +#ifndef RGFW_MEMSET + #define RGFW_MEMSET(ptr, value, num) memset(ptr, value, num) +#endif + +#ifndef RGFW_MEMCPY + #define RGFW_MEMCPY(dist, src, len) memcpy(dist, src, len) +#endif + +#ifndef RGFW_STRNCMP + #define RGFW_STRNCMP(s1, s2, max) strncmp(s1, s2, max) +#endif + +#ifndef RGFW_STRNCPY + #define RGFW_STRNCPY(dist, src, len) strncpy(dist, src, len) +#endif + +#ifndef RGFW_STRSTR + #define RGFW_STRSTR(str, substr) strstr(str, substr) +#endif + +#ifndef RGFW_STRTOL + /* required for X11 XDnD and X11 Monitor DPI */ + #include + #define RGFW_STRTOL(str, endptr, base) strtol(str, endptr, base) + #define RGFW_ATOF(num) atof(num) +#endif + +#if !defined(RGFW_PRINTF) && ( defined(RGFW_DEBUG) || defined(RGFW_WAYLAND) ) + /* required when using RGFW_DEBUG */ + #include + #define RGFW_PRINTF printf +#endif + +#ifndef RGFW_MAX_PATH + #define RGFW_MAX_PATH 260 /* max length of a path (for drag andn drop) */ +#endif +#ifndef RGFW_MAX_DROPS + #define RGFW_MAX_DROPS 260 /* max items you can drop at once */ +#endif + +#ifndef RGFW_MAX_EVENTS + #define RGFW_MAX_EVENTS 32 +#endif + +#ifndef RGFW_MAX_MONITORS + #define RGFW_MAX_MONITORS 6 +#endif + +#ifndef RGFW_COCOA_FRAME_NAME + #define RGFW_COCOA_FRAME_NAME NULL +#endif + +#ifdef RGFW_WIN95 /* for windows 95 testing (not that it really works) */ + #define RGFW_NO_PASSTHROUGH +#endif + +#if defined(RGFW_EXPORT) || defined(RGFW_IMPORT) + #if defined(_WIN32) + #if defined(__TINYC__) && (defined(RGFW_EXPORT) || defined(RGFW_IMPORT)) + #define __declspec(x) __attribute__((x)) + #endif + + #if defined(RGFW_EXPORT) + #define RGFWDEF __declspec(dllexport) + #else + #define RGFWDEF __declspec(dllimport) + #endif + #else + #if defined(RGFW_EXPORT) + #define RGFWDEF __attribute__((visibility("default"))) + #endif + #endif + #ifndef RGFWDEF + #define RGFWDEF + #endif +#endif + +#ifndef RGFWDEF + #ifdef RGFW_C89 + #define RGFWDEF __inline + #else + #define RGFWDEF inline + #endif +#endif + +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) + extern "C" { +#endif + +/* makes sure the header file part is only defined once by default */ +#ifndef RGFW_HEADER + +#define RGFW_HEADER + +#include +#ifndef RGFW_INT_DEFINED + #ifdef RGFW_USE_INT /* optional for any system that might not have stdint.h */ + typedef unsigned char u8; + typedef signed char i8; + typedef unsigned short u16; + typedef signed short i16; + typedef unsigned long int u32; + typedef signed long int i32; + typedef unsigned long long u64; + typedef signed long long i64; + #else /* use stdint standard types instead of c "standard" types */ + #include + + typedef uint8_t u8; + typedef int8_t i8; + typedef uint16_t u16; + typedef int16_t i16; + typedef uint32_t u32; + typedef int32_t i32; + typedef uint64_t u64; + typedef int64_t i64; + #endif + #define RGFW_INT_DEFINED +#endif + +typedef ptrdiff_t RGFW_ssize_t; + +#ifndef RGFW_BOOL_DEFINED + #define RGFW_BOOL_DEFINED + typedef u8 RGFW_bool; +#endif + +#define RGFW_BOOL(x) (RGFW_bool)((x) != 0) /* force a value to be 0 or 1 */ +#define RGFW_TRUE (RGFW_bool)1 +#define RGFW_FALSE (RGFW_bool)0 + +#define RGFW_ENUM(type, name) type name; enum +#define RGFW_BIT(x) (1 << (x)) + +#ifdef RGFW_VULKAN + + #if defined(RGFW_WAYLAND) && defined(RGFW_X11) + #define VK_USE_PLATFORM_WAYLAND_KHR + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE ((RGFW_usingWayland()) ? ("VK_KHR_wayland_surface") : ("VK_KHR_xlib_surface")) + #elif defined(RGFW_WAYLAND) + #define VK_USE_PLATFORM_WAYLAND_KHR + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE "VK_KHR_wayland_surface" + #elif defined(RGFW_X11) + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE "VK_KHR_xlib_surface" + #elif defined(RGFW_WINDOWS) + #define VK_USE_PLATFORM_WIN32_KHR + #define OEMRESOURCE + #define RGFW_VK_SURFACE "VK_KHR_win32_surface" + #elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) + #define VK_USE_PLATFORM_MACOS_MVK + #define RGFW_VK_SURFACE "VK_MVK_macos_surface" + #else + #define RGFW_VK_SURFACE NULL + #endif + +#endif + + +/*! @brief The stucture that contains information about the current RGFW instance */ +typedef struct RGFW_info RGFW_info; + +/*! @brief The window stucture for interfacing with the window */ +typedef struct RGFW_window RGFW_window; + +/*! @brief The source window stucture for interfacing with the underlying windowing API (e.g. winapi, wayland, cocoa, etc) */ +typedef struct RGFW_window_src RGFW_window_src; + +/*! @brief The color format for pixel data */ +typedef RGFW_ENUM(u8, RGFW_format) { + RGFW_formatRGB8 = 0, /*!< 8-bit RGB (3 channels) */ + RGFW_formatBGR8, /*!< 8-bit BGR (3 channels) */ + RGFW_formatRGBA8, /*!< 8-bit RGBA (4 channels) */ + RGFW_formatARGB8, /*!< 8-bit RGBA (4 channels) */ + RGFW_formatBGRA8, /*!< 8-bit BGRA (4 channels) */ + RGFW_formatABGR8, /*!< 8-bit BGRA (4 channels) */ + RGFW_formatCount +}; + +/*! @brief a stucture for interfacing with the underlying native image (e.g. XImage, HBITMAP, etc) */ +typedef struct RGFW_nativeImage RGFW_nativeImage; + +/*! @brief a stucture for interfacing with pixel data as a renderable surface */ +typedef struct RGFW_surface RGFW_surface; + +/*! @brief gamma struct for monitors */ +typedef struct RGFW_gammaRamp { + u16* red; /*!< array for the red channel */ + u16* green; /*!< array for the green channel */ + u16* blue; /*!< array for the blue channel */ + size_t count; /*! count of elements in each channel */ +} RGFW_gammaRamp; + +/*! @brief monitor mode data | can be changed by the user (with functions)*/ +typedef struct RGFW_monitorMode { + i32 w, h; /*!< monitor workarea size */ + float refreshRate; /*!< monitor refresh rate */ + u8 red, blue, green; /*!< sizeof rgb values */ + void* src; /*!< source API mode */ +} RGFW_monitorMode; + +/*! @brief structure for monitor node and source monitor data */ +typedef struct RGFW_monitorNode RGFW_monitorNode; + +/*! @brief structure for monitor data */ +typedef struct RGFW_monitor { + i32 x, y; /*!< x - y of the monitor workarea */ + char name[128]; /*!< monitor name */ + float scaleX, scaleY; /*!< monitor content scale */ + float pixelRatio; /*!< pixel ratio for monitor (1.0 for regular, 2.0 for hiDPI) */ + float physW, physH; /*!< monitor physical size in inches */ + RGFW_monitorMode mode; /*!< current mode of the monitor */ + void* userPtr; /*!< pointer for user data */ + RGFW_monitorNode* node; /*!< source node data of the monitor */ +} RGFW_monitor; + +/*! @brief what type of request you are making for the monitor */ +typedef RGFW_ENUM(u8, RGFW_modeRequest) { + RGFW_monitorScale = RGFW_BIT(0), /*!< scale the monitor size */ + RGFW_monitorRefresh = RGFW_BIT(1), /*!< change the refresh rate */ + RGFW_monitorRGB = RGFW_BIT(2), /*!< change the monitor RGB bits size */ + RGFW_monitorAll = RGFW_monitorScale | RGFW_monitorRefresh | RGFW_monitorRGB +}; + +/*! a raw pointer to the underlying mouse handle for setting and creating custom mouse icons */ +typedef void RGFW_mouse; + +/*! @brief RGFW's abstract keycodes */ +typedef RGFW_ENUM(u8, RGFW_key) { + RGFW_keyNULL = 0, + RGFW_escape = '\033', + RGFW_backtick = '`', + RGFW_0 = '0', + RGFW_1 = '1', + RGFW_2 = '2', + RGFW_3 = '3', + RGFW_4 = '4', + RGFW_5 = '5', + RGFW_6 = '6', + RGFW_7 = '7', + RGFW_8 = '8', + RGFW_9 = '9', + RGFW_minus = '-', + RGFW_equal = '=', + RGFW_equals = RGFW_equal, + RGFW_backSpace = '\b', + RGFW_tab = '\t', + RGFW_space = ' ', + RGFW_a = 'a', + RGFW_b = 'b', + RGFW_c = 'c', + RGFW_d = 'd', + RGFW_e = 'e', + RGFW_f = 'f', + RGFW_g = 'g', + RGFW_h = 'h', + RGFW_i = 'i', + RGFW_j = 'j', + RGFW_k = 'k', + RGFW_l = 'l', + RGFW_m = 'm', + RGFW_n = 'n', + RGFW_o = 'o', + RGFW_p = 'p', + RGFW_q = 'q', + RGFW_r = 'r', + RGFW_s = 's', + RGFW_t = 't', + RGFW_u = 'u', + RGFW_v = 'v', + RGFW_w = 'w', + RGFW_x = 'x', + RGFW_y = 'y', + RGFW_z = 'z', + RGFW_period = '.', + RGFW_comma = ',', + RGFW_slash = '/', + RGFW_bracket = '[', + RGFW_closeBracket = ']', + RGFW_semicolon = ';', + RGFW_apostrophe = '\'', + RGFW_backSlash = '\\', + RGFW_return = '\n', + RGFW_enter = RGFW_return, + RGFW_delete = '\177', /* 127 */ + RGFW_F1, + RGFW_F2, + RGFW_F3, + RGFW_F4, + RGFW_F5, + RGFW_F6, + RGFW_F7, + RGFW_F8, + RGFW_F9, + RGFW_F10, + RGFW_F11, + RGFW_F12, + RGFW_F13, + RGFW_F14, + RGFW_F15, + RGFW_F16, + RGFW_F17, + RGFW_F18, + RGFW_F19, + RGFW_F20, + RGFW_F21, + RGFW_F22, + RGFW_F23, + RGFW_F24, + RGFW_F25, + RGFW_capsLock, + RGFW_shiftL, + RGFW_controlL, + RGFW_altL, + RGFW_superL, + RGFW_shiftR, + RGFW_controlR, + RGFW_altR, + RGFW_superR, + RGFW_up, + RGFW_down, + RGFW_left, + RGFW_right, + RGFW_insert, + RGFW_menu, + RGFW_end, + RGFW_home, + RGFW_pageUp, + RGFW_pageDown, + RGFW_numLock, + RGFW_kpSlash, + RGFW_kpMultiply, + RGFW_kpPlus, + RGFW_kpMinus, + RGFW_kpEqual, + RGFW_kpEquals = RGFW_kpEqual, + RGFW_kp1, + RGFW_kp2, + RGFW_kp3, + RGFW_kp4, + RGFW_kp5, + RGFW_kp6, + RGFW_kp7, + RGFW_kp8, + RGFW_kp9, + RGFW_kp0, + RGFW_kpPeriod, + RGFW_kpReturn, + RGFW_scrollLock, + RGFW_printScreen, + RGFW_pause, + RGFW_world1, + RGFW_world2, + RGFW_keyLast = 256 /* padding for alignment ~(175 by default) */ +}; + +/*! @brief abstract mouse button codes */ +typedef RGFW_ENUM(u8, RGFW_mouseButton) { + RGFW_mouseLeft = 0, /*!< left mouse button is pressed */ + RGFW_mouseMiddle, /*!< mouse-wheel-button is pressed */ + RGFW_mouseRight, /*!< right mouse button is pressed */ + RGFW_mouseMisc1, RGFW_mouseMisc2, RGFW_mouseMisc3, RGFW_mouseMisc4, RGFW_mouseMisc5, + RGFW_mouseFinal +}; + +/*! abstract key modifier codes */ +typedef RGFW_ENUM(u8, RGFW_keymod) { + RGFW_modCapsLock = RGFW_BIT(0), + RGFW_modNumLock = RGFW_BIT(1), + RGFW_modControl = RGFW_BIT(2), + RGFW_modAlt = RGFW_BIT(3), + RGFW_modShift = RGFW_BIT(4), + RGFW_modSuper = RGFW_BIT(5), + RGFW_modScrollLock = RGFW_BIT(6) +}; + +/*! @brief codes for the event types that can be sent */ +typedef RGFW_ENUM(u8, RGFW_eventType) { + RGFW_eventNone = 0, /*!< no event has been sent */ + RGFW_keyPressed, /*!< a key has been pressed */ + RGFW_keyReleased, /*!< a key has been released */ + /*! key event note + the code of the key pressed is stored in + RGFW_event.key.value + !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! + + while a string version is stored in + RGFW_event.key.valueString + +/ RGFW_event.key.mod holds the current mod + this means if CapsLock, NumLock are active or not + */ + RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right) */ + RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right) */ + RGFW_mouseScroll, /*!< a mouse scroll event */ + RGFW_mousePosChanged, /*!< the position of the mouse has been changed */ + /*! mouse event note + the x and y of the mouse can be found in the vector, RGFW_x, y + + RGFW_event.button.value holds which mouse button was pressed + */ + RGFW_windowMoved, /*!< the window was moved (by the user) */ + RGFW_windowResized, /*!< the window was resized (by the user), [on WASM this means the browser was resized] */ + RGFW_focusIn, /*!< window is in focus now */ + RGFW_focusOut, /*!< window is out of focus now */ + RGFW_mouseEnter, /* mouse entered the window */ + RGFW_mouseLeave, /* mouse left the window */ + RGFW_windowRefresh, /* The window content needs to be refreshed */ + + /* attribs change event note + The event data is sent straight to the window structure + with win->x, win->y, win->w and win->h + */ + RGFW_quit, /*!< the user clicked the quit button */ + RGFW_dataDrop, /*!< a file has been dropped into the window */ + RGFW_dataDrag, /*!< the start of a drag and drop event, when the file is being dragged */ + /* drop data note + The x and y coords of the drop are stored in the vector RGFW_x, y + + RGFW_event.drop.count holds how many files were dropped + + This is also the size of the array which stores all the dropped file string, + RGFW_event.drop.files + */ + RGFW_windowMaximized, /*!< the window was maximized */ + RGFW_windowMinimized, /*!< the window was minimized */ + RGFW_windowRestored, /*!< the window was restored */ + RGFW_scaleUpdated, /*!< content scale factor changed */ + RGFW_monitorConnected, /*!< a monitor has been connected */ + RGFW_monitorDisconnected /*!< a monitor has been disconnected */ +}; + +/*! @brief flags for toggling wether or not an event should be processed */ +typedef RGFW_ENUM(u32, RGFW_eventFlag) { + RGFW_keyPressedFlag = RGFW_BIT(RGFW_keyPressed), + RGFW_keyReleasedFlag = RGFW_BIT(RGFW_keyReleased), + RGFW_mouseScrollFlag = RGFW_BIT(RGFW_mouseScroll), + RGFW_mouseButtonPressedFlag = RGFW_BIT(RGFW_mouseButtonPressed), + RGFW_mouseButtonReleasedFlag = RGFW_BIT(RGFW_mouseButtonReleased), + RGFW_mousePosChangedFlag = RGFW_BIT(RGFW_mousePosChanged), + RGFW_mouseEnterFlag = RGFW_BIT(RGFW_mouseEnter), + RGFW_mouseLeaveFlag = RGFW_BIT(RGFW_mouseLeave), + RGFW_windowMovedFlag = RGFW_BIT(RGFW_windowMoved), + RGFW_windowResizedFlag = RGFW_BIT(RGFW_windowResized), + RGFW_focusInFlag = RGFW_BIT(RGFW_focusIn), + RGFW_focusOutFlag = RGFW_BIT(RGFW_focusOut), + RGFW_windowRefreshFlag = RGFW_BIT(RGFW_windowRefresh), + RGFW_windowMaximizedFlag = RGFW_BIT(RGFW_windowMaximized), + RGFW_windowMinimizedFlag = RGFW_BIT(RGFW_windowMinimized), + RGFW_windowRestoredFlag = RGFW_BIT(RGFW_windowRestored), + RGFW_scaleUpdatedFlag = RGFW_BIT(RGFW_scaleUpdated), + RGFW_quitFlag = RGFW_BIT(RGFW_quit), + RGFW_dataDropFlag = RGFW_BIT(RGFW_dataDrop), + RGFW_dataDragFlag = RGFW_BIT(RGFW_dataDrag), + RGFW_monitorConnectedFlag = RGFW_BIT(RGFW_monitorConnected), + RGFW_monitorDisconnectedFlag = RGFW_BIT(RGFW_monitorDisconnected), + + RGFW_keyEventsFlag = RGFW_keyPressedFlag | RGFW_keyReleasedFlag, + RGFW_mouseEventsFlag = RGFW_mouseButtonPressedFlag | RGFW_mouseButtonReleasedFlag | RGFW_mousePosChangedFlag | RGFW_mouseEnterFlag | RGFW_mouseLeaveFlag | RGFW_mouseScrollFlag , + RGFW_windowEventsFlag = RGFW_windowMovedFlag | RGFW_windowResizedFlag | RGFW_windowRefreshFlag | RGFW_windowMaximizedFlag | RGFW_windowMinimizedFlag | RGFW_windowRestoredFlag | RGFW_scaleUpdatedFlag, + RGFW_focusEventsFlag = RGFW_focusInFlag | RGFW_focusOutFlag, + RGFW_dataDropEventsFlag = RGFW_dataDropFlag | RGFW_dataDragFlag, + RGFW_monitorEventsFlag = RGFW_monitorConnectedFlag | RGFW_monitorDisconnectedFlag, + RGFW_allEventFlags = RGFW_keyEventsFlag | RGFW_mouseEventsFlag | RGFW_windowEventsFlag | RGFW_focusEventsFlag | RGFW_dataDropEventsFlag | RGFW_quitFlag | RGFW_monitorEventsFlag +}; + +/*! Event structure(s) and union for checking/getting events */ + +/*! @brief common event data across all events */ +typedef struct RGFW_commonEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ +} RGFW_commonEvent; + +/*! @brief event data for any mouse button event (press/release) */ +typedef struct RGFW_mouseButtonEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + RGFW_mouseButton value; /* !< which mouse button was pressed */ +} RGFW_mouseButtonEvent; + +/*! @brief event data for any mouse scroll event */ +typedef struct RGFW_mouseScrollEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + float x, y; /*!< the raw mouse scroll value */ +} RGFW_mouseScrollEvent; + +/*! @brief event data for any mouse position event (RGFW_mousePosChanged) */ +typedef struct RGFW_mousePosEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + i32 x, y; /*!< mouse x, y of event (or drop point) */ + float vecX, vecY; /*!< raw mouse movement */ +} RGFW_mousePosEvent; + +/*! @brief event data for any key event (press/release) */ +typedef struct RGFW_keyEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + RGFW_key value; /*!< the physical key of the event, refers to where key is physically !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ + u8 sym; /*!< mapped key char of the event */ + RGFW_bool repeat; /*!< key press event repeated (the key is being held) */ + RGFW_keymod mod; +} RGFW_keyEvent; + +/*! @brief event data for any data drop event */ +typedef struct RGFW_dataDropEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + /* 260 max paths with a max length of 260 */ + char** files; /*!< dropped files */ + size_t count; /*!< how many files were dropped */ +} RGFW_dataDropEvent; + +/*! @brief event data for any data drag event */ +typedef struct RGFW_dataDragEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + i32 x, y; /*!< mouse x, y of event (or drop point) */ +} RGFW_dataDragEvent; + +/*! @brief event data for when the window scale (DPI) is updated */ +typedef struct RGFW_scaleUpdatedEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + float x, y; /*!< DPI scaling */ +} RGFW_scaleUpdatedEvent; + +/*! @brief event data for when a monitor is connected, disconnected or updated */ +typedef struct RGFW_monitorEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies to (for event queue events) */ + const RGFW_monitor* monitor; /*!< the monitor that this event applies to */ +} RGFW_monitorEvent; + +/*! @brief union for all of the event stucture types */ +typedef union RGFW_event { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_commonEvent common; /*!< common event data (e.g.) type and win */ + RGFW_mouseButtonEvent button; /*!< data for a button press/release */ + RGFW_mouseScrollEvent scroll; /*!< data for a mouse scroll */ + RGFW_mousePosEvent mouse; /*!< data for mouse motion events */ + RGFW_keyEvent key; /*!< data for key press/release/hold events */ + RGFW_dataDropEvent drop; /*!< dropping a file events */ + RGFW_dataDragEvent drag; /*!< data for dragging a file events */ + RGFW_scaleUpdatedEvent scale; /*!< data for dpi scaling update events */ + RGFW_monitorEvent monitor; /*!< data for monitor events */ +} RGFW_event; + +/*! + @!brief codes for for RGFW_the code is stupid and C++ waitForEvent + waitMS -> Allows the function to keep checking for events even after there are no more events + if waitMS == 0, the loop will not wait for events + if waitMS > 0, the loop will wait that many miliseconds after there are no more events until it returns + if waitMS == -1 or waitMS == the max size of an unsigned 32-bit int, the loop will not return until it gets another event +*/ +typedef RGFW_ENUM(i32, RGFW_eventWait) { + RGFW_eventNoWait = 0, + RGFW_eventWaitNext = -1 +}; + +/*! @brief optional bitwise arguments for making a windows, these can be OR'd together */ +typedef RGFW_ENUM(u32, RGFW_windowFlags) { + RGFW_windowNoBorder = RGFW_BIT(0), /*!< the window doesn't have a border */ + RGFW_windowNoResize = RGFW_BIT(1), /*!< the window cannot be resized by the user */ + RGFW_windowAllowDND = RGFW_BIT(2), /*!< the window supports drag and drop */ + RGFW_windowHideMouse = RGFW_BIT(3), /*! the window should hide the mouse (can be toggled later on using `RGFW_window_showMouse`) */ + RGFW_windowFullscreen = RGFW_BIT(4), /*!< the window is fullscreen by default */ + RGFW_windowTransparent = RGFW_BIT(5), /*!< the window is transparent (only properly works on X11 and MacOS, although it's meant for for windows) */ + RGFW_windowCenter = RGFW_BIT(6), /*! center the window on the screen */ + RGFW_windowRawMouse = RGFW_BIT(7), /*!< use raw mouse mouse on window creation */ + RGFW_windowScaleToMonitor = RGFW_BIT(8), /*! scale the window to the screen */ + RGFW_windowHide = RGFW_BIT(9), /*! the window is hidden */ + RGFW_windowMaximize = RGFW_BIT(10), /*!< maximize the window on creation */ + RGFW_windowCenterCursor = RGFW_BIT(11), /*!< center the cursor to the window on creation */ + RGFW_windowFloating = RGFW_BIT(12), /*!< create a floating window */ + RGFW_windowFocusOnShow = RGFW_BIT(13), /*!< focus the window when it's shown */ + RGFW_windowMinimize = RGFW_BIT(14), /*!< focus the window when it's shown */ + RGFW_windowFocus = RGFW_BIT(15), /*!< if the window is in focus */ + RGFW_windowCaptureMouse = RGFW_BIT(16), /*!< capture the mouse mouse mouse on window creation */ + RGFW_windowOpenGL = RGFW_BIT(17), /*!< create an OpenGL context (you can also do this manually with RGFW_window_createContext_OpenGL) */ + RGFW_windowEGL = RGFW_BIT(18), /*!< create an EGL context (you can also do this manually with RGFW_window_createContext_EGL) */ + RGFW_windowedFullscreen = RGFW_windowNoBorder | RGFW_windowMaximize, + RGFW_windowCaptureRawMouse = RGFW_windowCaptureMouse | RGFW_windowRawMouse +}; + +/*! @brief the types of icon to set */ +typedef RGFW_ENUM(u8, RGFW_icon) { + RGFW_iconTaskbar = RGFW_BIT(0), + RGFW_iconWindow = RGFW_BIT(1), + RGFW_iconBoth = RGFW_iconTaskbar | RGFW_iconWindow +}; + +/*! @brief standard mouse icons */ +typedef RGFW_ENUM(u8, RGFW_mouseIcons) { + RGFW_mouseNormal = 0, + RGFW_mouseArrow, + RGFW_mouseIbeam, + RGFW_mouseText = RGFW_mouseIbeam, + RGFW_mouseCrosshair, + RGFW_mousePointingHand, + RGFW_mouseResizeEW, + RGFW_mouseResizeNS, + RGFW_mouseResizeNWSE, + RGFW_mouseResizeNESW, + RGFW_mouseResizeNW, + RGFW_mouseResizeN, + RGFW_mouseResizeNE, + RGFW_mouseResizeE, + RGFW_mouseResizeSE, + RGFW_mouseResizeS, + RGFW_mouseResizeSW, + RGFW_mouseResizeW, + RGFW_mouseResizeAll, + RGFW_mouseNotAllowed, + RGFW_mouseWait, + RGFW_mouseProgress, + RGFW_mouseIconCount, + RGFW_mouseIconFinal = 16 /* padding for alignment */ +}; + +/*! @breif flash request type */ +typedef RGFW_ENUM(u8, RGFW_flashRequest) { + RGFW_flashCancel = 0, + RGFW_flashBriefly, + RGFW_flashUntilFocused +}; + +/*! @brief the type of debug message */ +typedef RGFW_ENUM(u8, RGFW_debugType) { + RGFW_typeError = 0, RGFW_typeWarning, RGFW_typeInfo +}; + +/*! @brief error codes for known failure types */ +typedef RGFW_ENUM(u8, RGFW_errorCode) { + RGFW_noError = 0, /*!< no error */ + RGFW_errOutOfMemory, + RGFW_errOpenGLContext, RGFW_errEGLContext, /*!< error with the OpenGL context */ + RGFW_errWayland, RGFW_errX11, + RGFW_errDirectXContext, + RGFW_errIOKit, + RGFW_errClipboard, + RGFW_errFailedFuncLoad, + RGFW_errBuffer, + RGFW_errMetal, + RGFW_errPlatform, + RGFW_errEventQueue, + RGFW_infoWindow, RGFW_infoBuffer, RGFW_infoGlobal, RGFW_infoOpenGL, + RGFW_warningWayland, RGFW_warningOpenGL +}; + +/*! @brief callback function type for debug messags */ +typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, const char* msg); + +/*! @brief RGFW_windowMoved, the window and its new rect value */ +typedef void (* RGFW_windowMovedfunc)(RGFW_window* win, i32 x, i32 y); +/*! @brief RGFW_windowResized, the window and its new rect value */ +typedef void (* RGFW_windowResizedfunc)(RGFW_window* win, i32 w, i32 h); +/*! @brief RGFW_windowRestored, the window and its new rect value */ +typedef void (* RGFW_windowRestoredfunc)(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); +/*! @brief RGFW_windowMaximized, the window and its new rect value */ +typedef void (* RGFW_windowMaximizedfunc)(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); +/*! @brief RGFW_windowMinimized, the window and its new rect value */ +typedef void (* RGFW_windowMinimizedfunc)(RGFW_window* win); +/*! @brief RGFW_quit, the window that was closed */ +typedef void (* RGFW_windowQuitfunc)(RGFW_window* win); +/*! @brief RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its in focus */ +typedef void (* RGFW_focusfunc)(RGFW_window* win, RGFW_bool inFocus); +/*! @brief RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ +typedef void (* RGFW_mouseNotifyfunc)(RGFW_window* win, i32 x, i32 y, RGFW_bool status); +/*! @brief RGFW_mousePosChanged, the window that the move happened on, and the new point of the mouse */ +typedef void (* RGFW_mousePosfunc)(RGFW_window* win, i32 x, i32 y, float vecX, float vecY); +/*! @brief RGFW_dataDrag, the window, the point of the drop on the windows */ +typedef void (* RGFW_dataDragfunc)(RGFW_window* win, i32 x, i32 y); +/*! @brief RGFW_windowRefresh, the window that needs to be refreshed */ +typedef void (* RGFW_windowRefreshfunc)(RGFW_window* win); +/*! @brief RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the mapped key, the physical key, the string version, the state of the mod keys, if it was a press (else it's a release) */ +typedef void (* RGFW_keyfunc)(RGFW_window* win, u8 key, u8 sym, RGFW_keymod mod, RGFW_bool repeat, RGFW_bool pressed); +/*! @brief RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +typedef void (* RGFW_mouseButtonfunc)(RGFW_window* win, RGFW_mouseButton button, RGFW_bool pressed); +/*! @brief RGFW_mouseScroll, the window that got the event, the x scroll value, the y scroll value */ +typedef void (* RGFW_mouseScrollfunc)(RGFW_window* win, float x, float y); +/*! @brief RGFW_dataDrop the window that had the drop, the drop data and the number of files dropped */ +typedef void (* RGFW_dataDropfunc)(RGFW_window* win, char** files, size_t count); +/*! @brief RGFW_scaleUpdated, the window the event was sent to, content scaleX, content scaleY */ +typedef void (* RGFW_scaleUpdatedfunc)(RGFW_window* win, float scaleX, float scaleY); +/*! @brief RGFW_monitorConnected / RGFW_monitorDisconnected, there was a monitor connected/disconnected */ +typedef void (* RGFW_monitorfunc)(RGFW_window* win, const RGFW_monitor* monitor, RGFW_bool connected); + +/*! @brief function pointer equivalent of void* */ +typedef void (*RGFW_proc)(void); + +#if defined(RGFW_OPENGL) + +/*! @brief abstract structure for interfacing with the underlying OpenGL API */ +typedef struct RGFW_glContext RGFW_glContext; + +/*! @brief abstract structure for interfacing with the underlying EGL API */ +typedef struct RGFW_eglContext RGFW_eglContext; + +/*! values for the releaseBehavior hint */ +typedef RGFW_ENUM(i32, RGFW_glReleaseBehavior) { + RGFW_glReleaseFlush = 0, /*!< flush the pipeline will be flushed when the context is release */ + RGFW_glReleaseNone /*!< do nothing on release */ +}; + +/*! values for the profile hint */ +typedef RGFW_ENUM(i32, RGFW_glProfile) { + RGFW_glCore = 0, /*!< the core OpenGL version, e.g. just support for that version */ + RGFW_glForwardCompatibility, /*!< only compatibility for newer versions of OpenGL as well as the requested version */ + RGFW_glCompatibility, /*!< allow compatibility for older versions of OpenGL as well as the requested version */ + RGFW_glES /*!< use OpenGL ES */ +}; + +/*! values for the renderer hint */ +typedef RGFW_ENUM(i32, RGFW_glRenderer) { + RGFW_glAccelerated = 0, /*!< hardware accelerated (GPU) */ + RGFW_glSoftware /*!< software rendered (CPU) */ +}; + +/*! OpenGL initalization hints */ +typedef struct RGFW_glHints { + i32 stencil; /*!< set stencil buffer bit size (0 by default) */ + i32 samples; /*!< set number of sample buffers (0 by default) */ + i32 stereo; /*!< hint the context to use stereoscopic frame buffers for 3D (false by default) */ + i32 auxBuffers; /*!< number of aux buffers (0 by default) */ + i32 doubleBuffer; /*!< request double buffering (true by default) */ + i32 red, green, blue, alpha; /*!< set color bit sizes (all 8 by default) */ + i32 depth; /*!< set depth buffer bit size (24 by default) */ + i32 accumRed, accumGreen, accumBlue, accumAlpha; /*!< set accumulated RGBA bit sizes (all 0 by default) */ + RGFW_bool sRGB; /*!< request sRGA format (false by default) */ + RGFW_bool robustness; /*!< request a "robust" (as in memory-safe) context (false by default). For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_robustness.txt */ + RGFW_bool debug; /*!< request OpenGL debugging (false by default). */ + RGFW_bool noError; /*!< request no OpenGL errors (false by default). This causes OpenGL errors to be undefined behavior. For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/KHR/KHR_no_error.txt */ + RGFW_glReleaseBehavior releaseBehavior; /*!< hint how the OpenGL driver should behave when changing contexts (RGFW_glReleaseNone by default). For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/KHR/KHR_context_flush_control.txt */ + RGFW_glProfile profile; /*!< set OpenGL API profile (RGFW_glCore by default) */ + i32 major, minor; /*!< set the OpenGL API profile version (by default RGFW_glMajor is 1, RGFW_glMinor is 0) */ + RGFW_glContext* share; /*!< Share this OpenGL context with newly created OpenGL contexts; defaults to NULL. */ + RGFW_eglContext* shareEGL; /*!< Share this EGL context with newly created OpenGL contexts; defaults to NULL. */ + RGFW_glRenderer renderer; /*!< renderer to use e.g. accelerated or software defaults to accelerated */ +} RGFW_glHints; + +#endif + +/**! + * @brief Allocates memory using the allocator defined by RGFW_ALLOC at compile time. + * @param size The size (in bytes) of the memory block to allocate. + * @return A pointer to the allocated memory block. +*/ +RGFWDEF void* RGFW_alloc(size_t size); + +/**! + * @brief Frees memory using the deallocator defined by RGFW_FREE at compile time. + * @param ptr A pointer to the memory block to free. +*/ +RGFWDEF void RGFW_free(void* ptr); + +/**! + * @brief Returns the size (in bytes) of the RGFW_window structure. + * @return The size of the RGFW_window structure. +*/ +RGFWDEF size_t RGFW_sizeofWindow(void); + +/**! + * @brief Returns the size (in bytes) of the RGFW_window_src structure. + * @return The size of the RGFW_window_src structure. +*/ +RGFWDEF size_t RGFW_sizeofWindowSrc(void); + +/**! + * @brief (Unix) Toggles the use of Wayland. + * This is enabled by default when compiled with `RGFW_WAYLAND`. + * If not using `RGFW_WAYLAND`, Wayland functions are not exposed. + * This function can be used to force the use of XWayland. + * @param wayland A boolean value indicating whether to use Wayland (true) or not (false). +*/ +RGFWDEF void RGFW_useWayland(RGFW_bool wayland); + +/**! + * @brief Checks if Wayland is currently being used. + * @return RGFW_TRUE if using Wayland, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_usingWayland(void); + +/**! + * @brief Retrieves the current Cocoa layer (macOS only). + * @return A pointer to the Cocoa layer, or NULL if the platform is not in use. +*/ +RGFWDEF void* RGFW_getLayer_OSX(void); + +/**! + * @brief Retrieves the current X11 display connection. + * @return A pointer to the X11 display, or NULL if the platform is not in use. +*/ +RGFWDEF void* RGFW_getDisplay_X11(void); + +/**! + * @brief Retrieves the current Wayland display connection. + * @return A pointer to the Wayland display (`struct wl_display*`), or NULL if the platform is not in use. +*/ +RGFWDEF struct wl_display* RGFW_getDisplay_Wayland(void); + +/**! + * @brief Sets the class name for X11 and WinAPI windows. + * Windows with the same class name will be grouped by the window manager. + * By default, the class name matches the root window’s name. + * @param name The class name to assign. +*/ +RGFWDEF void RGFW_setClassName(const char* name); + +/**! + * @brief Sets the X11 instance name. + * By default, the window name will be used as the instance name. + * @param name The X11 instance name to set. +*/ +RGFWDEF void RGFW_setXInstName(const char* name); + +/**! + * @brief (macOS only) Changes the current working directory to the application’s resource folder. +*/ +RGFWDEF void RGFW_moveToMacOSResourceDir(void); + +/*! copy image to another image, respecting each image's format */ +RGFWDEF void RGFW_copyImageData(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, u8* src_data, RGFW_format src_format); + +/**! + * @brief Returns the size (in bytes) of the RGFW_nativeImage structure. + * @return The size of the RGFW_nativeImage structure. +*/ +RGFWDEF size_t RGFW_sizeofNativeImage(void); + +/**! + * @brief Returns the size (in bytes) of the RGFW_surface structure. + * @return The size of the RGFW_surface structure. +*/ +RGFWDEF size_t RGFW_sizeofSurface(void); + +/**! + * @brief Creates a new surface from raw pixel data. + * @param data A pointer to the pixel data buffer. + * @param w The width of the surface in pixels. + * @param h The height of the surface in pixels. + * @param format The pixel format of the data. + * @return A pointer to the newly created RGFW_surface. + * + * NOTE: when you create a surface using RGFW_createSurface / ptr, on X11 it uses the root window's visual + * this means it may fail to render on any other window if the visual does not match + * RGFW_window_createSurface and RGFW_window_createSurfacePtr exist only for X11 to address this issues + * Of course, you can also manually set the root window with RGFW_setRootWindow +*/ +RGFWDEF RGFW_surface* RGFW_createSurface(u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief Creates a surface using a pre-allocated RGFW_surface structure. + * @param data A pointer to the pixel data buffer. + * @param w The width of the surface in pixels. + * @param h The height of the surface in pixels. + * @param format The pixel format of the data. + * @param surface A pointer to a pre-allocated RGFW_surface structure. + * @return RGFW_TRUE if successful, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); + +/**! + * @brief Retrieves the native image associated with a surface. + * @param surface A pointer to the RGFW_surface. + * @return A pointer to the native RGFW_nativeImage associated with the surface. +*/ +RGFWDEF RGFW_nativeImage* RGFW_surface_getNativeImage(RGFW_surface* surface); + +/**! + * @brief Frees the surface pointer and any buffers used for software rendering. + * @param surface A pointer to the RGFW_surface to free. +*/ +RGFWDEF void RGFW_surface_free(RGFW_surface* surface); + +/**! + * @brief Frees only the internal buffers used for software rendering, leaving the surface struct intact. + * @param surface A pointer to the RGFW_surface whose buffers should be freed. +*/ +RGFWDEF void RGFW_surface_freePtr(RGFW_surface* surface); + + +/**! + * @brief Loads a mouse icon from bitmap data (similar to RGFW_window_setIcon). + * @param data A pointer to the bitmap pixel data. + * @param w The width of the mouse icon in pixels. + * @param h The height of the mouse icon in pixels. + * @param format The pixel format of the data. + * @return A pointer to the newly loaded RGFW_mouse structure. + * + * @note The icon is not resized by default. +*/ +RGFWDEF RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief Frees the data associated with an RGFW_mouse structure. + * @param mouse A pointer to the RGFW_mouse to free. +*/ +RGFWDEF void RGFW_freeMouse(RGFW_mouse* mouse); + +/**! + * @brief Get an allocated array of the supported modes of a monitor + * @param monitor the source monitor object + * @param count [OUTPUT] the count of the array + * @return the allocated array of supported modes +*/ +RGFWDEF RGFW_monitorMode* RGFW_monitor_getModes(RGFW_monitor* monitor, size_t* count); + +/**! + * @brief Free RGFW allocated modes array + * @param monitor the source monitor object + * @param modes a pointer to an allocated array of modes +*/ +RGFWDEF void RGFW_freeModes(RGFW_monitorMode* modes); +/**! + * @brief Get the supported modes of a monitor using a pre-allocated array + * @param monitor the source monitor object + * @param modes [OUTPUT] a pointer to an allocated array of modes + * @return the number of (possible) modes, if [modes == NULL] the possible nodes *may* be less than the actual modes +*/ +RGFWDEF size_t RGFW_monitor_getModesPtr(RGFW_monitor* monitor, RGFW_monitorMode** modes); + +/**! + * @brief Get the allocated gamma ramp + * @param monitor the source monitor object +*/ +RGFWDEF RGFW_gammaRamp* RGFW_monitor_getGammaRamp(RGFW_monitor* monitor); + +/**! + * @brief Free the gamma ramp allocated by RGFW + * @param allocated gamma ramp +*/ +RGFWDEF void RGFW_freeGammaRamp(RGFW_gammaRamp* ramp); + +/**! + * @brief Get the monitor's gamma ramp using a pre-allocated struct with allocated data + * @param monitor the source monitor object + * @param ramp [OUTPUT] a pointer to an allocated gamma ramp (can be NULL to just get the count) + * @return the count of the gamma ramp +*/ +RGFWDEF size_t RGFW_monitor_getGammaRampPtr(RGFW_monitor* monitor, RGFW_gammaRamp* ramp); + +/**! + * @brief Set the monitor's gamma ramp using a pre-allocated struct with allocated data + * @param monitor the source monitor object + * @param ramp a pointer to an allocated gamma ramp + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_setGammaRamp(RGFW_monitor* monitor, RGFW_gammaRamp* ramp); + +/**! + * @brief Create and set the monitor's gamma ramp with a base gamma exponent + * @param monitor the source monitor object + * @param the gamma exponent + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_setGamma(RGFW_monitor* monitor, float gamma); + +/**! + * @brief Create and set the monitor's gamma ramp with a base gamma exponent using a pre-allocated array + * @param monitor the source monitor object + * @param gamma the gamma exponent + * @param pre-allocated gammaramp channel + * @param count the length of the allocated channel array + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_setGammaPtr(RGFW_monitor* monitor, float gamma, u16* ptr, size_t count); + +/**! + * @brief Get the workarea of a monitor, meaning the parts not occupied by OS graphics (i.e. the taskbar) + * @param monitor the source monitor object + * @param x [OUTPUT] the x pos of the workarea + * @param y [OUTPUT] the y pos of the workarea + * @param w [OUTPUT] the width of the workarea + * @param h [OUTPUT] the height of the workarea + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_getWorkarea(RGFW_monitor* monitor, i32* x, i32* y, i32* width, i32* height); + +/**! + * @brief Get the position of a monitor (the same as monitor.x / monitor.y) + * @param x [OUTPUT] the x position of the monitor + * @param y [OUTPUT] the y position of the monitor + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_getPosition(RGFW_monitor* monitor, i32* x, i32* y); + +/**! + * @brief Get the name of a monitor (the same as monitor.name) + * @return the cstring of the monitor's name +*/ +RGFWDEF const char* RGFW_monitor_getName(RGFW_monitor* monitor); + +/**! + * @brief Get the scale of a monitor (the same as monitor.scaleX / monitor.scaleY) + * @param monitor the source monitor object + * @param x [OUTPUT] the x scale of the monitor + * @param y [OUTPUT] the y scale of the monitor + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_getScale(RGFW_monitor* monitor, float* x, float* y); + +/**! + * @brief Get the physical size of a monitor (the same as monitor.physW / monitor.physH) + * @param monitor the source monitor object + * @param w [OUTPUT] the physical width of the monitor + * @param h [OUTPUT] the physical height of the monitor + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_getPhysicalSize(RGFW_monitor* monitor, float* w, float* h); + +/**! + * @brief Set the user pointer of a monitor (the same as monitor.userPtr = userPtr) + * @param monitor the source monitor object + * @param userPtr the new user pointer for the monitor +*/ +RGFWDEF void RGFW_monitor_setUserPtr(RGFW_monitor* monitor, void* userPtr); + +/**! + * @brief Get the user pointer of a monitor (the same as monitor.userPtr) + * @param monitor the source monitor object + * @return the user pointer of the monitor +*/ +RGFWDEF void* RGFW_monitor_getUserPtr(RGFW_monitor* monitor); + +/**! + * @brief Get the mode of a monitor (the same as monitor.mode) + * @param monitor the source monitor object + * @param mode [OUTPUT] current mode the monitor + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_monitor_getMode(RGFW_monitor* monitor, RGFW_monitorMode* mode); + +/**! + * @brief Poll and check for monitor updates (this is called internally on monitor update events and RGFW_init) +*/ +RGFWDEF void RGFW_pollMonitors(void); + +/**! + * @brief Retrieves an array of all available monitors. + * @param len [OUTPUT] A pointer to store the number of monitors found (maximum of RGFW_MAX_MONITORS [6 by default]). + * @return An array of pointers to RGFW_monitor structures. +*/ +RGFWDEF RGFW_monitor** RGFW_getMonitors(size_t* len); + +/**! + * @brief Retrieves the primary monitor. + * @return A pointer to the RGFW_monitor structure representing the primary monitor. +*/ +RGFWDEF RGFW_monitor* RGFW_getPrimaryMonitor(void); + +/**! + * @brief Requests the display mode for a monitor (based on what attributes are directly requested). + * @param mon The monitor to apply the mode change to. + * @param mode The desired RGFW_monitorMode. + * @param request The RGFW_modeRequest describing how to handle the mode change. + * @return RGFW_TRUE if the mode was successfully applied, otherwise RGFW_FALSE. +*/ +RGFWDEF RGFW_bool RGFW_monitor_requestMode(RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request); + +/**! + * @brief Sets a specific display mode for a monitor directly. + * @param mon The monitor to apply the mode change to. + * @param mode The desired RGFW_monitorMode. + * @param request The RGFW_modeRequest describing how to handle the mode change. + * @return RGFW_TRUE if the mode was successfully applied, otherwise RGFW_FALSE. +*/ +RGFWDEF RGFW_bool RGFW_monitor_setMode(RGFW_monitor* mon, RGFW_monitorMode* mode); + +/**! + * @brief Compares two monitor modes to check if they are equivalent. + * @param mon The first monitor mode. + * @param mon2 The second monitor mode. + * @param request The RGFW_modeRequest that defines the comparison parameters. + * @return RGFW_TRUE if both modes are equivalent, otherwise RGFW_FALSE. +*/ +RGFWDEF RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode* mon, RGFW_monitorMode* mon2, RGFW_modeRequest request); + +/**! + * @brief Scales a monitor’s mode to match a window’s size. + * @param mon The monitor to be scaled. + * @param win The window whose size should be used as a reference. + * @return RGFW_TRUE if the scaling was successful, otherwise RGFW_FALSE. +*/ +RGFWDEF RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor* mon, struct RGFW_window* win); + + /**! + * @brief set (enable or disable) raw mouse mode globally + * @param the boolean state of raw mouse mode + * +*/ +RGFWDEF void RGFW_setRawMouseMode(RGFW_bool state); + +/**! +* @brief sleep until RGFW gets an event or the timer ends (defined by OS) +* @param waitMS how long to wait for the next event (in miliseconds) +*/ +RGFWDEF void RGFW_waitForEvent(i32 waitMS); + +/**! +* @brief Set if events should be queued or not (enabled by default if the event queue is checked) +* @param queue boolean value if RGFW should queue events or not +*/ +RGFWDEF void RGFW_setQueueEvents(RGFW_bool queue); + +/**! +* @brief check all the events until there are none left and updates window structure attributes +*/ +RGFWDEF void RGFW_pollEvents(void); + +/**! +* @brief check all the events until there are none left and updates window structure attributes +* queues events if the queue is checked and/or requested +*/ +RGFWDEF void RGFW_stopCheckEvents(void); + +/**! + * @brief polls and pops the next event + * @param event [OUTPUT] a pointer to store the retrieved event + * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise + * + * NOTE: Using this function without a loop may cause event lag. + * For multi-threaded systems, use RGFW_pollEvents combined with RGFW_checkQueuedEvent. + * + * Example: + * RGFW_event event; + * while (RGFW_checkEvent(win, &event)) { + * // handle event + * } +*/ +RGFWDEF RGFW_bool RGFW_checkEvent(RGFW_event* event); + +/**! + * @brief pops the first queued event + * @param event [OUTPUT] a pointer to store the retrieved event + * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_checkQueuedEvent(RGFW_event* event); + +/** * @defgroup Input +* @{ */ + +/**! + * @brief returns true if the key is pressed during the current frame + * @param key the key code of the key you want to check + * @return The boolean value if the key is pressed or not +*/ +RGFWDEF RGFW_bool RGFW_isKeyPressed(RGFW_key key); + +/**! + * @brief returns true if the key was released during the current frame + * @param key the key code of the key you want to check + * @return The boolean value if the key is released or not +*/ +RGFWDEF RGFW_bool RGFW_isKeyReleased(RGFW_key key); + +/**! + * @brief returns true if the key is down + * @param key the key code of the key you want to check + * @return The boolean value if the key is down or not +*/ +RGFWDEF RGFW_bool RGFW_isKeyDown(RGFW_key key); + +/**! + * @brief returns true if the mouse button is pressed during the current frame + * @param button the mouse button code of the button you want to check + * @return The boolean value if the button is pressed or not +*/ +RGFWDEF RGFW_bool RGFW_isMousePressed(RGFW_mouseButton button); + +/**! + * @brief returns true if the mouse button is released during the current frame + * @param button the mouse button code of the button you want to check + * @return The boolean value if the button is released or not +*/ +RGFWDEF RGFW_bool RGFW_isMouseReleased(RGFW_mouseButton button); + +/**! + * @brief returns true if the mouse button is down + * @param button the mouse button code of the button you want to check + * @return The boolean value if the button is down or not +*/ +RGFWDEF RGFW_bool RGFW_isMouseDown(RGFW_mouseButton button); + +/**! + * @brief outputs the current x, y position of the mouse + * @param X [OUTPUT] a pointer for the output X value + * @param Y [OUTPUT] a pointer for the output Y value +*/ +RGFWDEF void RGFW_getMouseScroll(float* x, float* y); + +/**! + * @brief outputs the current x, y movement vector of the mouse + * @param X [OUTPUT] a pointer for the output X vector value + * @param Y [OUTPUT] a pointer for the output Y vector value +*/ +RGFWDEF void RGFW_getMouseVector(float* x, float* y); +/** @} */ + +/**! + * @brief creates a new window + * @param name the requested title of the window + * @param x the requested x position of the window + * @param y the requested y position of the window + * @param w the requested width of the window + * @param h the requested height of the window + * @param flags extra arguments ((u32)0 means no flags used) + * @return A pointer to the newly created window structure + * + * NOTE: (windows) if the executable has an icon resource named RGFW_ICON, it will be set as the initial icon for the window +*/ +RGFWDEF RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags); + +/**! + * @brief creates a new window using a pre-allocated window structure + * @param name the requested title of the window + * @param x the requested x position of the window + * @param y the requested y position of the window + * @param w the requested width of the window + * @param h the requested height of the window + * @param flags extra arguments ((u32)0 means no flags used) + * @param win a pointer the pre-allocated window structure + * @return A pointer to the newly created window structure +*/ +RGFWDEF RGFW_window* RGFW_createWindowPtr(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags, RGFW_window* win); + +/**! + * @brief creates a new surface structure + * @param win the source window of the surface + * @param data a pointer to the raw data of the structure (you allocate this) + * @param w the width the data + * @param h the height of the data + * @return A pointer to the newly created surface structure + * + * NOTE: when you create a surface using RGFW_createSurface / ptr, on X11 it uses the root window's visual + * this means it may fail to render on any other window if the visual does not match + * RGFW_window_createSurface and RGFW_window_createSurfacePtr exist only for X11 to address this issues + * Of course, you can also manually set the root window with RGFW_setRootWindow + */ +RGFWDEF RGFW_surface* RGFW_window_createSurface(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief creates a new surface structure using a pre-allocated surface structure + * @param win the source window of the surface + * @param data a pointer to the raw data of the structure (you allocate this) + * @param w the width the data + * @param h the height of the data + * @param a pointer to the pre-allocated surface structure + * @return a bool if the creation was successful or not +*/ +RGFWDEF RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); + +/**! + * @brief blits a surface stucture to the window + * @param win a pointer the window to blit to + * @param surface a pointer to the surface +*/ +RGFWDEF void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface); + +/**! + * @brief gets the position of the window | with RGFW_window.x and window.y + * @param x [OUTPUT] the x position of the window + * @param y [OUTPUT] the y position of the window + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_window_getPosition(RGFW_window* win, i32* x, i32* y); /*!< */ + +/**! + * @brief gets the size of the window | with RGFW_window.w and window.h + * @param win a pointer to the window + * @param w [OUTPUT] the width of the window + * @param h [OUTPUT] the height of the window + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_window_getSize(RGFW_window* win, i32* w, i32* h); + +/**! + * @brief gets the size of the window in exact pixels + * @param win a pointer to the window + * @param w [OUTPUT] the width of the window + * @param h [OUTPUT] the height of the window + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_window_getSizeInPixels(RGFW_window* win, i32* w, i32* h); + +/**! + * @brief gets the flags of the window | returns RGFW_window._flags + * @param win a pointer to the window + * @return the window flags +*/ +RGFWDEF u32 RGFW_window_getFlags(RGFW_window* win); + +/**! + * @brief returns the exit key assigned to the window + * @param win a pointer to the target window + * @return The key code assigned as the exit key +*/ +RGFWDEF RGFW_key RGFW_window_getExitKey(RGFW_window* win); + +/**! + * @brief sets the exit key for the window + * @param win a pointer to the target window + * @param key the key code to assign as the exit key +*/ +RGFWDEF void RGFW_window_setExitKey(RGFW_window* win, RGFW_key key); + +/**! + * @brief sets the types of events you want the window to receive + * @param win a pointer to the target window + * @param events the event flags to enable (use RGFW_allEventFlags for all) +*/ +RGFWDEF void RGFW_window_setEnabledEvents(RGFW_window* win, RGFW_eventFlag events); + +/**! + * @brief gets the currently enabled events for the window + * @param win a pointer to the target window + * @return The enabled event flags for the window +*/ +RGFWDEF RGFW_eventFlag RGFW_window_getEnabledEvents(RGFW_window* win); + +/**! + * @brief enables all events and disables selected ones + * @param win a pointer to the target window + * @param events the event flags to disable +*/ +RGFWDEF void RGFW_window_setDisabledEvents(RGFW_window* win, RGFW_eventFlag events); + +/**! + * @brief directly enables or disables a specific event or group of events + * @param win a pointer to the target window + * @param event the event flag or group of flags to modify + * @param state RGFW_TRUE to enable, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setEventState(RGFW_window* win, RGFW_eventFlag event, RGFW_bool state); + +/**! + * @brief gets the user pointer associated with the window + * @param win a pointer to the target window + * @return The user-defined pointer stored in the window +*/ +RGFWDEF void* RGFW_window_getUserPtr(RGFW_window* win); + +/**! + * @brief sets a user pointer for the window + * @param win a pointer to the target window + * @param ptr a pointer to associate with the window +*/ +RGFWDEF void RGFW_window_setUserPtr(RGFW_window* win, void* ptr); + +/**! + * @brief retrieves the platform-specific window source pointer + * @param win a pointer to the target window + * @return A pointer to the internal RGFW_window_src structure +*/ +RGFWDEF RGFW_window_src* RGFW_window_getSrc(RGFW_window* win); + +/**! + * @brief sets the macOS layer object associated with the window + * @param win a pointer to the target window + * @param layer a pointer to the macOS layer object + * @note Only available on macOS platforms +*/ +RGFWDEF void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer); + +/**! + * @brief retrieves the macOS view object associated with the window + * @param win a pointer to the target window + * @return A pointer to the macOS view object, or NULL if not on macOS +*/ +RGFWDEF void* RGFW_window_getView_OSX(RGFW_window* win); + +/**! + * @brief retrieves the macOS window object + * @param win a pointer to the target window + * @return A pointer to the macOS window object, or NULL if not on macOS +*/ +RGFWDEF void* RGFW_window_getWindow_OSX(RGFW_window* win); + +/**! + * @brief retrieves the HWND handle for the window + * @param win a pointer to the target window + * @return A pointer to the Windows HWND handle, or NULL if not on Windows +*/ +RGFWDEF void* RGFW_window_getHWND(RGFW_window* win); + +/**! + * @brief retrieves the HDC handle for the window + * @param win a pointer to the target window + * @return A pointer to the Windows HDC handle, or NULL if not on Windows +*/ +RGFWDEF void* RGFW_window_getHDC(RGFW_window* win); + +/**! + * @brief retrieves the X11 Window handle for the window + * @param win a pointer to the target window + * @return The X11 Window handle, or 0 if not on X11 +*/ +RGFWDEF u64 RGFW_window_getWindow_X11(RGFW_window* win); + +/**! + * @brief retrieves the Wayland surface handle for the window + * @param win a pointer to the target window + * @return A pointer to the Wayland wl_surface, or NULL if not on Wayland +*/ +RGFWDEF struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win); + +/** * @defgroup Window_management +* @{ */ + +/*! set the window flags (will undo flags if they don't match the old ones) */ +RGFWDEF void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags); + +/**! + * @brief polls and pops the next event with the matching target window in event queue, pushes back events that don't match + * @param win a pointer to the target window + * @param event [OUTPUT] a pointer to store the retrieved event + * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise + * + * NOTE: Using this function without a loop may cause event lag. + * For multi-threaded systems, use RGFW_pollEvents combined with RGFW_window_checkQueuedEvent. + * + * Example: + * RGFW_event event; + * while (RGFW_window_checkEvent(win, &event)) { + * // handle event + * } +*/ +RGFWDEF RGFW_bool RGFW_window_checkEvent(RGFW_window* win, RGFW_event* event); + +/**! + * @brief pops the first queued event with the matching target window, pushes back events that don't match + * @param win a pointer to the target window + * @param event [OUTPUT] a pointer to store the retrieved event + * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_checkQueuedEvent(RGFW_window* win, RGFW_event* event); + +/**! + * @brief checks if a key was pressed while the window is in focus + * @param win a pointer to the target window + * @param key the key code to check + * @return RGFW_TRUE if the key was pressed, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isKeyPressed(RGFW_window* win, RGFW_key key); + +/**! + * @brief checks if a key is currently being held down + * @param win a pointer to the target window + * @param key the key code to check + * @return RGFW_TRUE if the key is held down, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isKeyDown(RGFW_window* win, RGFW_key key); + +/**! + * @brief checks if a key was released + * @param win a pointer to the target window + * @param key the key code to check + * @return RGFW_TRUE if the key was released, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isKeyReleased(RGFW_window* win, RGFW_key key); + +/**! + * @brief checks if a mouse button was pressed + * @param win a pointer to the target window + * @param button the mouse button code to check + * @return RGFW_TRUE if the mouse button was pressed, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMousePressed(RGFW_window* win, RGFW_mouseButton button); + +/**! + * @brief checks if a mouse button is currently held down + * @param win a pointer to the target window + * @param button the mouse button code to check + * @return RGFW_TRUE if the mouse button is down, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseDown(RGFW_window* win, RGFW_mouseButton button); + +/**! + * @brief checks if a mouse button was released + * @param win a pointer to the target window + * @param button the mouse button code to check + * @return RGFW_TRUE if the mouse button was released, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseReleased(RGFW_window* win, RGFW_mouseButton button); + +/**! + * @brief checks if the mouse left the window (true only for the first frame) + * @param win a pointer to the target window + * @return RGFW_TRUE if the mouse left, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_didMouseLeave(RGFW_window* win); + +/**! + * @brief checks if the mouse entered the window (true only for the first frame) + * @param win a pointer to the target window + * @return RGFW_TRUE if the mouse entered, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_didMouseEnter(RGFW_window* win); + +/**! + * @brief checks if the mouse is currently inside the window bounds + * @param win a pointer to the target window + * @return RGFW_TRUE if the mouse is inside, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseInside(RGFW_window* win); + +/**! + * @brief checks if there is data being dragged into or within the window + * @param win a pointer to the target window + * @return RGFW_TRUE if data is being dragged, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isDataDragging(RGFW_window* win); + +/**! + * @brief gets the position of a data drag + * @param win a pointer to the target window + * @param x [OUTPUT] pointer to store the x position + * @param y [OUTPUT] pointer to store the y position + * @return RGFW_TRUE if there is an active drag, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_getDataDrag(RGFW_window* win, i32* x, i32* y); + +/**! + * @brief checks if a data drop occurred in the window (first frame only) + * @param win a pointer to the target window + * @return RGFW_TRUE if data was dropped, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_didDataDrop(RGFW_window* win); + +/**! + * @brief retrieves files from a data drop (drag and drop) + * @param win a pointer to the target window + * @param files [OUTPUT] a pointer to the array of file paths + * @param count [OUTPUT] the number of dropped files + * @return RGFW_TRUE if a data drop occurred, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_getDataDrop(RGFW_window* win, const char*** files, size_t* count); + +/**! + * @brief closes the window and frees its associated structure + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_close(RGFW_window* win); + +/**! + * @brief closes the window without freeing its structure + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_closePtr(RGFW_window* win); + +/**! + * @brief moves the window to a new position on the screen + * @param win a pointer to the target window + * @param x the new x position + * @param y the new y position +*/ +RGFWDEF void RGFW_window_move(RGFW_window* win, i32 x, i32 y); + +/**! + * @brief moves the window to a specific monitor + * @param win a pointer to the target window + * @param m the target monitor +*/ +RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor* m); + +/**! + * @brief resizes the window to the given dimensions + * @param win a pointer to the target window + * @param w the new width + * @param h the new height +*/ +RGFWDEF void RGFW_window_resize(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets the aspect ratio of the window + * @param win a pointer to the target window + * @param w the width ratio + * @param h the height ratio +*/ +RGFWDEF void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets the minimum size of the window + * @param win a pointer to the target window + * @param w the minimum width + * @param h the minimum height +*/ +RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets the maximum size of the window + * @param win a pointer to the target window + * @param w the maximum width + * @param h the maximum height +*/ +RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets focus to the window + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_focus(RGFW_window* win); + +/**! + * @brief checks if the window is currently in focus + * @param win a pointer to the target window + * @return RGFW_TRUE if the window is in focus, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isInFocus(RGFW_window* win); + +/**! + * @brief raises the window to the top of the stack + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_raise(RGFW_window* win); + +/**! + * @brief maximizes the window + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_maximize(RGFW_window* win); + +/**! + * @brief toggles fullscreen mode for the window + * @param win a pointer to the target window + * @param fullscreen RGFW_TRUE to enable fullscreen, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen); + +/**! + * @brief centers the window on the screen + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_center(RGFW_window* win); + +/**! + * @brief minimizes the window + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_minimize(RGFW_window* win); + +/**! + * @brief restores the window from minimized state + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_restore(RGFW_window* win); + +/**! + * @brief makes the window a floating window + * @param win a pointer to the target window + * @param floating RGFW_TRUE to float, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating); + +/**! + * @brief sets the opacity level of the window + * @param win a pointer to the target window + * @param opacity the opacity level (0–255) +*/ +RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); + +/**! + * @brief toggles window borders + * @param win a pointer to the target window + * @param border RGFW_TRUE for bordered, RGFW_FALSE for borderless +*/ +RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); + +/**! + * @brief checks if the window is borderless + * @param win a pointer to the target window + * @return RGFW_TRUE if borderless, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); + +/**! + * @brief toggles drag-and-drop (DND) support for the window + * @param win a pointer to the target window + * @param allow RGFW_TRUE to allow DND, RGFW_FALSE to disable + * @note RGFW_windowAllowDND must still be passed when creating the window +*/ +RGFWDEF void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow); + +/**! + * @brief checks if drag-and-drop (DND) is allowed + * @param win a pointer to the target window + * @return RGFW_TRUE if DND is enabled, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_allowsDND(RGFW_window* win); + +#ifndef RGFW_NO_PASSTHROUGH +/**! + * @brief toggles mouse passthrough for the window + * @param win a pointer to the target window + * @param passthrough RGFW_TRUE to enable passthrough, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough); +#endif + +/**! + * @brief renames the window + * @param win a pointer to the target window + * @param name the new title string for the window +*/ +RGFWDEF void RGFW_window_setName(RGFW_window* win, const char* name); + +/**! + * @brief sets the icon for the window and taskbar + * @param win a pointer to the target window + * @param data the image data + * @param w the width of the icon + * @param h the height of the icon + * @param format the image format + * @return RGFW_TRUE if successful, RGFW_FALSE otherwise + * + * NOTE: The image may be resized by default. +*/ +RGFWDEF RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief sets the icon for the window and/or taskbar + * @param win a pointer to the target window + * @param data the image data + * @param w the width of the icon + * @param h the height of the icon + * @param format the image format + * @param type the target icon type (taskbar, window, or both) + * @return RGFW_TRUE if successful, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type); + +/**! + * @brief sets the mouse icon for the window using a loaded bitmap + * @param win a pointer to the target window + * @param mouse a pointer to the RGFW_mouse struct containing the icon +*/ +RGFWDEF void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse); + +/**! + * @brief Sets the mouse to a standard system cursor. + * @param win The target window. + * @param mouse The standard cursor type (see RGFW_MOUSE enum). + * @return True if the standard cursor was successfully applied. +*/ +RGFWDEF RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, RGFW_mouseIcons mouse); + +/**! + * @brief Sets the mouse to the default cursor icon. + * @param win The target window. + * @return True if the default cursor was successfully set. +*/ +RGFWDEF RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win); + +/**! + * @brief set (enable or disable) raw mouse mode only for the select window + * @param win The target window. + * @param the boolean state of raw mouse mode + * +*/ +RGFWDEF void RGFW_window_setRawMouseMode(RGFW_window* win, RGFW_bool state); + +/**! + * @brief lock/unlock the cursor. + * @param win The target window. + * @param the boolean state of the mouse's capture state + * +*/ +RGFWDEF void RGFW_window_captureMouse(RGFW_window* win, RGFW_bool state); + +/**! + * @brief lock/unlock the cursor and enable raw mpuise mode. + * @param win The target window. + * @param the boolean state of raw mouse mode + * +*/ +RGFWDEF void RGFW_window_captureRawMouse(RGFW_window* win, RGFW_bool state); + +/**! + * @brief Returns true if the mouse is using raw mouse mode + * @param win The target window. + * @return True if the mouse is using raw mouse input mode. +*/ +RGFWDEF RGFW_bool RGFW_window_isRawMouseMode(RGFW_window* win); + + +/**! + * @brief Returns true if the mouse is captured + * @param win The target window. + * @return True if the mouse is being captured. +*/ +RGFWDEF RGFW_bool RGFW_window_isCaptured(RGFW_window* win); + +/**! + * @brief Hides the window from view. + * @param win The target window. +*/ +RGFWDEF void RGFW_window_hide(RGFW_window* win); + +/**! + * @brief Shows the window if it was hidden. + * @param win The target window. +*/ +RGFWDEF void RGFW_window_show(RGFW_window* win); + +/**! + * @breif request a window flash to get attention from the user + * @param win the target window + * @param request the flash operation requested +*/ +RGFWDEF void RGFW_window_flash(RGFW_window* win, RGFW_flashRequest request); + +/**! + * @brief Sets whether the window should close. + * @param win The target window. + * @param shouldClose True to signal the window should close, false to keep it open. + * + * This can override or trigger the `RGFW_window_shouldClose` state by modifying window flags. +*/ +RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose); + +/**! + * @brief Retrieves the current global mouse position. + * @param x [OUTPUT] Pointer to store the X position of the mouse on the screen. + * @param y [OUTPUT] Pointer to store the Y position of the mouse on the screen. + * @return True if the position was successfully retrieved. +*/ +RGFWDEF RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y); + +/**! + * @brief Retrieves the mouse position relative to the window. + * @param win The target window. + * @param x [OUTPUT] Pointer to store the X position within the window. + * @param y [OUTPUT] Pointer to store the Y position within the window. + * @return True if the position was successfully retrieved. +*/ +RGFWDEF RGFW_bool RGFW_window_getMouse(RGFW_window* win, i32* x, i32* y); + +/**! + * @brief Shows or hides the mouse cursor for the window. + * @param win The target window. + * @param show True to show the mouse, false to hide it. +*/ +RGFWDEF void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show); + +/**! + * @brief Checks if the mouse is currently hidden in the window. + * @param win The target window. + * @return True if the mouse is hidden. +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseHidden(RGFW_window* win); + +/**! + * @brief Moves the mouse to the specified position within the window. + * @param win The target window. + * @param x The new X position. + * @param y The new Y position. +*/ +RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y); + +/**! + * @brief Checks if the window should close. + * @param win The target window. + * @return True if the window should close (for example, if ESC was pressed or a close event occurred). +*/ +RGFWDEF RGFW_bool RGFW_window_shouldClose(RGFW_window* win); + +/**! + * @brief Checks if the window is currently fullscreen. + * @param win The target window. + * @return True if the window is fullscreen. +*/ +RGFWDEF RGFW_bool RGFW_window_isFullscreen(RGFW_window* win); + +/**! + * @brief Checks if the window is currently hidden. + * @param win The target window. + * @return True if the window is hidden. +*/ +RGFWDEF RGFW_bool RGFW_window_isHidden(RGFW_window* win); + +/**! + * @brief Checks if the window is minimized. + * @param win The target window. + * @return True if the window is minimized. +*/ +RGFWDEF RGFW_bool RGFW_window_isMinimized(RGFW_window* win); + +/**! + * @brief Checks if the window is maximized. + * @param win The target window. + * @return True if the window is maximized. +*/ +RGFWDEF RGFW_bool RGFW_window_isMaximized(RGFW_window* win); + +/**! + * @brief Checks if the window is floating. + * @param win The target window. + * @return True if the window is floating. +*/ +RGFWDEF RGFW_bool RGFW_window_isFloating(RGFW_window* win); +/** @} */ + +/** * @defgroup Monitor +* @{ */ + +/**! + * @brief Scales the window to match its monitor’s resolution. + * @param win The target window. + * + * This function is automatically called when the flag `RGFW_scaleToMonitor` + * is used during window creation. +*/ +RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); + +/**! + * @brief Retrieves the monitor structure associated with the window. + * @param win The target window. + * @return The monitor structure of the window. +*/ +RGFWDEF RGFW_monitor* RGFW_window_getMonitor(RGFW_window* win); + +/** @} */ + +/** * @defgroup Clipboard +* @{ */ + +/**! + * @brief Reads clipboard data. + * @param size [OUTPUT] A pointer that will be filled with the size of the clipboard data. + * @return A pointer to the clipboard data as a string. +*/ +RGFWDEF const char* RGFW_readClipboard(size_t* size); + +/**! + * @brief Reads clipboard data into a provided buffer, or returns the required length if str is NULL. + * @param str [OUTPUT] A pointer to the buffer that will receive the clipboard data (or NULL to get required size). + * @param strCapacity The capacity of the provided buffer. + * @return The number of bytes read or required length of clipboard data. +*/ +RGFWDEF RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity); + +/**! + * @brief Writes text to the clipboard. + * @param text The text to be written to the clipboard. + * @param textLen The length of the text being written. +*/ +RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); +/** @} */ + + + +/** * @defgroup error handling +* @{ */ +/**! + * @brief Sets the callback function to handle debug messages from RGFW. + * @param func The function pointer to be used as the debug callback. + * @return The previously set debug callback function. +*/ +RGFWDEF RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func); + +/**! + * @brief Sends a debug message manually through the currently set debug callback. + * @param type The type of debug message being sent. + * @param err The associated error code. + * @param msg The debug message text. +*/ +RGFWDEF void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, const char* msg); +/** @} */ + +/** + + + event callbacks. + These are completely optional, so you can use the normal + RGFW_checkEvent() method if you prefer that + +* @defgroup Callbacks +* @{ +*/ + +/**! + * @brief Sets the callback function for window move events. + * @param func The function to be called when the window is moved. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowMovedfunc RGFW_setWindowMovedCallback(RGFW_windowMovedfunc func); + +/**! + * @brief Sets the callback function for window resize events. + * @param func The function to be called when the window is resized. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowResizedCallback(RGFW_windowResizedfunc func); + +/**! + * @brief Sets the callback function for window quit events. + * @param func The function to be called when the window receives a quit signal. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowQuitfunc RGFW_setWindowQuitCallback(RGFW_windowQuitfunc func); + +/**! + * @brief Sets the callback function for mouse move events. + * @param func The function to be called when the mouse moves within the window. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mousePosfunc RGFW_setMousePosCallback(RGFW_mousePosfunc func); + +/**! + * @brief Sets the callback function for window refresh events. + * @param func The function to be called when the window needs to be refreshed. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowRefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowRefreshfunc func); + +/**! + * @brief Sets the callback function for focus change events. + * @param func The function to be called when the window gains or loses focus. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); + +/**! + * @brief Sets the callback function for mouse notification events. + * @param func The function to be called when a mouse notification event occurs. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallback(RGFW_mouseNotifyfunc func); + +/**! + * @brief Sets the callback function for data drop events. + * @param func The function to be called when data is dropped into the window. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_dataDropfunc RGFW_setDataDropCallback(RGFW_dataDropfunc func); + +/**! + * @brief Sets the callback function for the start of a data drag event. + * @param func The function to be called when data dragging begins. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_dataDragfunc RGFW_setDataDragCallback(RGFW_dataDragfunc func); + +/**! + * @brief Sets the callback function for key press and release events. + * @param func The function to be called when a key is pressed or released. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); + +/**! + * @brief Sets the callback function for mouse button press and release events. + * @param func The function to be called when a mouse button is pressed or released. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mouseButtonfunc RGFW_setMouseButtonCallback(RGFW_mouseButtonfunc func); + +/**! + * @brief Sets the callback function for mouse scroll events. + * @param func The function to be called when the mouse wheel is scrolled. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mouseScrollfunc RGFW_setMouseScrollCallback(RGFW_mouseScrollfunc func); + +/**! + * @brief Sets the callback function for window maximize events. + * @param func The function to be called when the window is maximized. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowMaximizedfunc RGFW_setWindowMaximizedCallback(RGFW_windowMaximizedfunc func); + +/**! + * @brief Sets the callback function for window minimize events. + * @param func The function to be called when the window is minimized. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowMinimizedfunc RGFW_setWindowMinimizedCallback(RGFW_windowMinimizedfunc func); + +/**! + * @brief Sets the callback function for window restore events. + * @param func The function to be called when the window is restored from a minimized or maximized state. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowRestoredfunc RGFW_setWindowRestoredCallback(RGFW_windowRestoredfunc func); + +/**! + * @brief Sets the callback function for DPI (scale) update events. + * @param func The function to be called when the window’s DPI or scale changes. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_scaleUpdatedfunc RGFW_setScaleUpdatedCallback(RGFW_scaleUpdatedfunc func); + +/**! + * @brief Sets the callback function for monitor connected and disconnect events. + * @param func The function to be called when a monitor is connected or disconnected. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_monitorfunc RGFW_setMonitorCallback(RGFW_monitorfunc func); + +/** @} */ + +/** * @defgroup graphics_API +* @{ */ + +/*! native rendering API functions */ +#if defined(RGFW_OPENGL) +/* these are native opengl specific functions and will NOT work with EGL */ + +/*!< make the window the current OpenGL drawing context + + NOTE: + if you want to switch the graphics context's thread, + you have to run RGFW_window_makeCurrentContext_OpenGL(NULL); on the old thread + then RGFW_window_makeCurrentContext_OpenGL(valid_window) on the new thread +*/ + +/**! + * @brief Sets the global OpenGL hints to the specified pointer. + * @param hints A pointer to the RGFW_glHints structure containing the desired OpenGL settings. +*/ +RGFWDEF void RGFW_setGlobalHints_OpenGL(RGFW_glHints* hints); + +/**! + * @brief Resets the global OpenGL hints to their default values. +*/ +RGFWDEF void RGFW_resetGlobalHints_OpenGL(void); + +/**! + * @brief Gets the current global OpenGL hints pointer. + * @return A pointer to the currently active RGFW_glHints structure. +*/ +RGFWDEF RGFW_glHints* RGFW_getGlobalHints_OpenGL(void); + +/**! + * @brief Creates and allocates an OpenGL context for the specified window. + * @param win A pointer to the target RGFW_window. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return A pointer to the newly created RGFW_glContext. +*/ +RGFWDEF RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints); + +/**! + * @brief Creates an OpenGL context for the specified window using a preallocated context structure. + * @param win A pointer to the target RGFW_window. + * @param ctx A pointer to an already allocated RGFW_glContext structure. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return RGFW_TRUE on success, RGFW_FALSE on failure. +*/ +RGFWDEF RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints); + +/**! + * @brief Retrieves the OpenGL context associated with a window. + * @param win A pointer to the RGFW_window. + * @return A pointer to the associated RGFW_glContext, or NULL if none exists or if the context is EGL-based. +*/ +RGFWDEF RGFW_glContext* RGFW_window_getContext_OpenGL(RGFW_window* win); + +/**! + * @brief Deletes and frees the OpenGL context. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_glContext to delete. + * + * @note This is automatically called by RGFW_window_close if the window’s context is not NULL. +*/ +RGFWDEF void RGFW_window_deleteContext_OpenGL(RGFW_window* win, RGFW_glContext* ctx); + +/**! + * @brief Deletes the OpenGL context without freeing its memory. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_glContext to delete. + * + * @note This is automatically called by RGFW_window_close if the window’s context is not NULL. +*/ +RGFWDEF void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx); + +/**! + * @brief Retrieves the native source context from an RGFW_glContext. + * @param ctx A pointer to the RGFW_glContext. + * @return A pointer to the native OpenGL context handle. +*/ +RGFWDEF void* RGFW_glContext_getSourceContext(RGFW_glContext* ctx); + +/**! + * @brief Makes the specified window the current OpenGL rendering target. + * @param win A pointer to the RGFW_window to make current. + * + * @note This is typically called internally by RGFW_window_makeCurrent. +*/ +RGFWDEF void RGFW_window_makeCurrentWindow_OpenGL(RGFW_window* win); + +/**! + * @brief Makes the OpenGL context of the specified window current. + * @param win A pointer to the RGFW_window whose context should be made current. + * + * @note To move a context between threads, call RGFW_window_makeCurrentContext_OpenGL(NULL) + * on the old thread before making it current on the new one. +*/ +RGFWDEF void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win); + +/**! + * @brief Swaps the OpenGL buffers for the specified window. + * @param win A pointer to the RGFW_window whose buffers should be swapped. +*/ +RGFWDEF void RGFW_window_swapBuffers_OpenGL(RGFW_window* win); + +/**! + * @brief Retrieves the current OpenGL context. + * @return A pointer to the currently active OpenGL context (GLX, WGL, Cocoa, or WebGL backend). +*/ +RGFWDEF void* RGFW_getCurrentContext_OpenGL(void); + +/**! + * @brief Retrieves the current OpenGL window. + * @return A pointer to the RGFW_window currently bound as the OpenGL context target. +*/ +RGFWDEF RGFW_window* RGFW_getCurrentWindow_OpenGL(void); + +/**! + * @brief Sets the OpenGL swap interval (vsync). + * @param win A pointer to the RGFW_window. + * @param swapInterval The desired swap interval value (0 to disable vsync, 1 to enable). +*/ +RGFWDEF void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval); + +/**! + * @brief Retrieves the address of a native OpenGL procedure. + * @param procname The name of the OpenGL function to look up. + * @return A pointer to the function, or NULL if not found. +*/ +RGFWDEF RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname); + +/**! + * @brief Checks whether a specific OpenGL or OpenGL ES API extension is supported. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupported_OpenGL(const char* extension, size_t len); + +/**! + * @brief Checks whether a specific platform-dependent OpenGL extension is supported. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len); + +/* these are EGL specific functions, they may fallback to OpenGL */ +#ifdef RGFW_EGL +/**! + * @brief Creates and allocates an OpenGL/EGL context for the specified window. + * @param win A pointer to the target RGFW_window. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return A pointer to the newly created RGFW_eglContext. +*/ +RGFWDEF RGFW_eglContext* RGFW_window_createContext_EGL(RGFW_window* win, RGFW_glHints* hints); + +/**! + * @brief Creates an OpenGL/EGL context for the specified window using a preallocated context structure. + * @param win A pointer to the target RGFW_window. + * @param ctx A pointer to an already allocated RGFW_eglContext structure. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return RGFW_TRUE on success, RGFW_FALSE on failure. +*/ +RGFWDEF RGFW_bool RGFW_window_createContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx, RGFW_glHints* hints); + +/**! + * @brief Frees and deletes an OpenGL/EGL context. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_eglContext to delete. + * + * @note Automatically called by RGFW_window_close if RGFW owns the context. +*/ +RGFWDEF void RGFW_window_deleteContext_EGL(RGFW_window* win, RGFW_eglContext* ctx); + +/**! + * @brief Deletes an OpenGL/EGL context without freeing its memory. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_eglContext to delete. + * + * @note Automatically called by RGFW_window_close if RGFW owns the context. +*/ +RGFWDEF void RGFW_window_deleteContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx); + +/**! + * @brief Retrieves the OpenGL/EGL context associated with a window. + * @param win A pointer to the RGFW_window. + * @return A pointer to the associated RGFW_eglContext, or NULL if none exists or if the context is a native OpenGL context. +*/ +RGFWDEF RGFW_eglContext* RGFW_window_getContext_EGL(RGFW_window* win); + +/**! + * @brief Retrieves the EGL display handle. + * @return A pointer to the native EGLDisplay. +*/ +RGFWDEF void* RGFW_getDisplay_EGL(void); + +/**! + * @brief Retrieves the native source context from an RGFW_eglContext. + * @param ctx A pointer to the RGFW_eglContext. + * @return A pointer to the native EGLContext handle. +*/ +RGFWDEF void* RGFW_eglContext_getSourceContext(RGFW_eglContext* ctx); + +/**! + * @brief Retrieves the EGL surface handle from an RGFW_eglContext. + * @param ctx A pointer to the RGFW_eglContext. + * @return A pointer to the EGLSurface associated with the context. +*/ +RGFWDEF void* RGFW_eglContext_getSurface(RGFW_eglContext* ctx); + +/**! + * @brief Retrieves the Wayland EGL window handle from an RGFW_eglContext. + * @param ctx A pointer to the RGFW_eglContext. + * @return A pointer to the wl_egl_window associated with the EGL context. +*/ +RGFWDEF struct wl_egl_window* RGFW_eglContext_wlEGLWindow(RGFW_eglContext* ctx); + +/**! + * @brief Swaps the EGL buffers for the specified window. + * @param win A pointer to the RGFW_window whose buffers should be swapped. + * + * @note Typically called by RGFW_window_swapInterval. +*/ +RGFWDEF void RGFW_window_swapBuffers_EGL(RGFW_window* win); + +/**! + * @brief Makes the specified window the current EGL rendering target. + * @param win A pointer to the RGFW_window to make current. + * + * @note This is typically called internally by RGFW_window_makeCurrent. +*/ +RGFWDEF void RGFW_window_makeCurrentWindow_EGL(RGFW_window* win); + +/**! + * @brief Makes the EGL context of the specified window current. + * @param win A pointer to the RGFW_window whose context should be made current. + * + * @note To move a context between threads, call RGFW_window_makeCurrentContext_EGL(NULL) + * on the old thread before making it current on the new one. +*/ +RGFWDEF void RGFW_window_makeCurrentContext_EGL(RGFW_window* win); + +/**! + * @brief Retrieves the current EGL context. + * @return A pointer to the currently active EGLContext. +*/ +RGFWDEF void* RGFW_getCurrentContext_EGL(void); + +/**! + * @brief Retrieves the current EGL window. + * @return A pointer to the RGFW_window currently bound as the EGL context target. +*/ +RGFWDEF RGFW_window* RGFW_getCurrentWindow_EGL(void); + +/**! + * @brief Sets the EGL swap interval (vsync). + * @param win A pointer to the RGFW_window. + * @param swapInterval The desired swap interval value (0 to disable vsync, 1 to enable). +*/ +RGFWDEF void RGFW_window_swapInterval_EGL(RGFW_window* win, i32 swapInterval); + +/**! + * @brief Retrieves the address of a native OpenGL or OpenGL ES procedure in an EGL context. + * @param procname The name of the OpenGL function to look up. + * @return A pointer to the function, or NULL if not found. +*/ +RGFWDEF RGFW_proc RGFW_getProcAddress_EGL(const char* procname); + +/**! + * @brief Checks whether a specific OpenGL or OpenGL ES API extension is supported in the current EGL context. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupported_EGL(const char* extension, size_t len); + +/**! + * @brief Checks whether a specific platform-dependent EGL extension is supported in the current context. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform_EGL(const char* extension, size_t len); +#endif +#endif + +#ifdef RGFW_VULKAN +#include + +/* if you don't want to use the above macros */ + +/**! + * @brief Retrieves the Vulkan instance extensions required by RGFW. + * @param count [OUTPUT] A pointer that will receive the number of required extensions (typically 2). + * @return A pointer to a static array of required Vulkan instance extension names. +*/ +RGFWDEF const char** RGFW_getRequiredInstanceExtensions_Vulkan(size_t* count); + +/**! + * @brief Creates a Vulkan surface for the specified window. + * @param win A pointer to the RGFW_window for which to create the Vulkan surface. + * @param instance The Vulkan instance used to create the surface. + * @param surface [OUTPUT] A pointer to a VkSurfaceKHR handle that will receive the created surface. + * @return A VkResult indicating success or failure. +*/ +RGFWDEF VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface); + +/**! + * @brief Checks whether the specified Vulkan physical device and queue family support presentation for RGFW. + * @param instance The Vulkan instance. + * @param physicalDevice The Vulkan physical device to check. + * @param queueFamilyIndex The index of the queue family to query for presentation support. + * @return RGFW_TRUE if presentation is supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_getPresentationSupport_Vulkan(VkPhysicalDevice physicalDevice, u32 queueFamilyIndex); +#endif + +#ifdef RGFW_DIRECTX +#ifndef RGFW_WINDOWS + #undef RGFW_DIRECTX +#else + #define OEMRESOURCE + #include + + #ifndef __cplusplus + #define __uuidof(T) IID_##T + #endif +/**! + * @brief Creates a DirectX swap chain for the specified RGFW window. + * @param win A pointer to the RGFW_window for which to create the swap chain. + * @param pFactory A pointer to the IDXGIFactory used to create the swap chain. + * @param pDevice A pointer to the DirectX device (e.g., ID3D11Device or ID3D12Device). + * @param swapchain [OUTPUT] A pointer to an IDXGISwapChain pointer that will receive the created swap chain. + * @return An integer result code (0 on success, or a DirectX error code on failure). +*/ +RGFWDEF int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain); +#endif +#endif + +#ifdef RGFW_WEBGPU + #include + /**! + * @brief Creates a WebGPU surface for the specified RGFW window. + * @param window A pointer to the RGFW_window for which to create the surface. + * @param instance The WebGPU instance used to create the surface. + * @return The created WGPUSurface handle. + */ + RGFWDEF WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance); +#endif + +/** @} */ + +/** * @defgroup Supporting +* @{ */ + +/**! + * @brief Sets the root (main) RGFW window. + * @param win A pointer to the RGFW_window to set as the root window. +*/ +RGFWDEF void RGFW_setRootWindow(RGFW_window* win); + +/**! + * @brief Retrieves the current root RGFW window. + * @return A pointer to the current root RGFW_window. +*/ +RGFWDEF RGFW_window* RGFW_getRootWindow(void); + +/**! + * @brief Pushes an event into the standard RGFW event queue. + * @param event A pointer to the RGFW_event to be added to the queue. +*/ +RGFWDEF void RGFW_eventQueuePush(const RGFW_event* event); + +/**! + * @brief Clears all events from the RGFW event queue without processing them. +*/ +RGFWDEF void RGFW_eventQueueFlush(void); + +/**! + * @brief Pops the next event from the RGFW event queue. + * @return A pointer to the popped RGFW_event, or NULL if the queue is empty. +*/ +RGFWDEF RGFW_event* RGFW_eventQueuePop(void); + +/**! + * @brief Pops the next event from the RGFW event queue that matches the target window, pushes back events that don't matchj. + * @param win A pointer to the target RGFW_window. + * @return A pointer to the popped RGFW_event, or NULL if the queue is empty. +*/ +RGFWDEF RGFW_event* RGFW_window_eventQueuePop(RGFW_window* win); + +/**! + * @brief Converts an API keycode to the RGFW unmapped (physical) key. + * @param keycode The platform-specific keycode. + * @return The corresponding RGFW keycode. +*/ +RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); + +/**! + * @brief Converts an RGFW keycode to the unmapped (physical) API key. + * @param keycode The RGFW keycode. + * @return The corresponding platform-specific keycode. +*/ +RGFWDEF u32 RGFW_rgfwToApiKey(u32 keycode); + +/**! + * @brief Converts an RGFW keycode to the mapped character representation. + * @param keycode The RGFW keycode. + * @return The corresponding key character. +*/ +RGFWDEF u8 RGFW_rgfwToKeyChar(u32 keycode); + +/**! + * @brief Retrieves the size of the RGFW_info structure. + * @return The size (in bytes) of RGFW_info. +*/ +RGFWDEF size_t RGFW_sizeofInfo(void); + +/**! + * @brief Initializes the RGFW library. + * @return 0 on success, or a negative error code on failure. + * @note This is automatically called when the first window is created. +*/ +RGFWDEF i32 RGFW_init(void); + +/**! + * @brief Deinitializes the RGFW library. + * @note This is automatically called when the last open window is closed. +*/ +RGFWDEF void RGFW_deinit(void); + +/**! + * @brief Initializes RGFW using a user-provided RGFW_info structure. + * @param info A pointer to an RGFW_info structure to be used for initialization. + * @return 0 on success, or a negative error code on failure. +*/ +RGFWDEF i32 RGFW_init_ptr(RGFW_info* info); + +/**! + * @brief Deinitializes a specific RGFW instance stored in the provided RGFW_info pointer. + * @param info A pointer to the RGFW_info structure representing the instance to deinitialize. +*/ +RGFWDEF void RGFW_deinit_ptr(RGFW_info* info); + +/**! + * @brief Sets the global RGFW_info structure pointer. + * @param info A pointer to the RGFW_info structure to set. +*/ +RGFWDEF void RGFW_setInfo(RGFW_info* info); + +/**! + * @brief Retrieves the global RGFW_info structure pointer. + * @return A pointer to the current RGFW_info structure. +*/ +RGFWDEF RGFW_info* RGFW_getInfo(void); + +/** @} */ +#endif /* RGFW_HEADER */ + +#if !defined(RGFW_NATIVE_HEADER) && (defined(RGFW_NATIVE) || defined(RGFW_IMPLEMENTATION)) +#define RGFW_NATIVE_HEADER + #if (defined(RGFW_OPENGL) || defined(RGFW_WEGL)) && defined(_MSC_VER) + #pragma comment(lib, "opengl32") + #endif + + #ifdef RGFW_OPENGL + struct RGFW_eglContext { + void* ctx; + void* surface; + struct wl_egl_window* eglWindow; + }; + + typedef union RGFW_gfxContext { + RGFW_glContext* native; + RGFW_eglContext* egl; + } RGFW_gfxContext; + + typedef RGFW_ENUM(u32, RGFW_gfxContextType) { + RGFW_gfxNativeOpenGL = RGFW_BIT(0), + RGFW_gfxEGL = RGFW_BIT(1), + RGFW_gfxOwnedByRGFW = RGFW_BIT(2) + }; + #endif + + /*! source data for the window (used by the APIs) */ + #ifdef RGFW_WINDOWS + + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef OEMRESOURCE + #define OEMRESOURCE + #endif + + #include + + struct RGFW_nativeImage { + HBITMAP bitmap; + u8* bitmapBits; + RGFW_format format; + HDC hdcMem; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { HGLRC ctx; }; + #endif + + struct RGFW_window_src { + HWND window; /*!< source window */ + HDC hdc; /*!< source HDC */ + i32 offsetW, offsetH; /*!< width and height offset for window */ + HICON hIconSmall, hIconBig; /*!< source window icons */ + i32 maxSizeW, maxSizeH, minSizeW, minSizeH, aspectRatioW, aspectRatioH; /*!< for setting max/min resize (RGFW_WINDOWS) */ + RGFW_bool actionFrame; /* frame after a caption button was toggled (e.g. minimize, maximize or close) */ + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif + }; + +#elif defined(RGFW_UNIX) + #ifdef RGFW_X11 + #include + #include + + #ifndef RGFW_NO_XRANDR + #include + #include + #endif + #endif + + #ifdef RGFW_WAYLAND + #ifdef RGFW_LIBDECOR + #include + #endif + + #include + #include + #endif + + struct RGFW_nativeImage { + #ifdef RGFW_X11 + XImage* bitmap; + #endif + #ifdef RGFW_WAYLAND + struct wl_buffer* wl_buffer; + i32 fd; + struct wl_shm_pool* pool; + #endif + u8* buffer; + RGFW_format format; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { + #ifdef RGFW_X11 + struct __GLXcontextRec* ctx; /*!< source graphics context */ + Window window; + #endif + #ifdef RGFW_WAYLAND + RGFW_eglContext egl; + #endif + }; + #endif + + struct RGFW_window_src { + i32 x, y, w, h; + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif +#ifdef RGFW_X11 + Window window; /*!< source window */ + Window parent; /*!< parent window */ + GC gc; + u64 flashEnd; + #ifdef RGFW_ADVANCED_SMOOTH_RESIZE + i64 counter_value; + XID counter; + #endif +#endif /* RGFW_X11 */ + +#if defined(RGFW_WAYLAND) + struct wl_surface* surface; + struct xdg_surface* xdg_surface; + struct xdg_toplevel* xdg_toplevel; + struct zxdg_toplevel_decoration_v1* decoration; + struct zwp_locked_pointer_v1 *locked_pointer; + struct xdg_toplevel_icon_v1 *icon; + u32 decoration_mode; + /* State flags to configure the window */ + RGFW_bool pending_activated; + RGFW_bool activated; + RGFW_bool resizing; + RGFW_bool pending_maximized; + RGFW_bool maximized; + RGFW_bool minimized; + + RGFW_bool using_custom_cursor; + struct wl_surface* custom_cursor_surface; + + RGFW_monitorNode* active_monitor; + + struct wl_data_source *data_source; // offer data to other clients + + #ifdef RGFW_LIBDECOR + struct libdecor* decorContext; + #endif +#endif /* RGFW_WAYLAND */ + }; + +#elif defined(RGFW_MACOS) + #include + + struct RGFW_nativeImage { + RGFW_format format; + u8* buffer; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { + void* ctx; + void* format; + }; + #endif + + struct RGFW_window_src { + void* window; + void* view; /* apple viewpoint thingy */ + void* mouse; + void* delegate; + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif + }; + +#elif defined(RGFW_WASM) + + #include + #include + + struct RGFW_nativeImage { + RGFW_format format; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; + }; + #endif + + struct RGFW_window_src { + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif + }; + +#endif + +struct RGFW_surface { + u8* data; + i32 w, h; + RGFW_format format; + RGFW_nativeImage native; +}; + +/*! internal window data that is not specific to the OS */ +typedef struct RGFW_windowInternal { + /*! which key RGFW_window_shouldClose checks. Settting this to RGFW_keyNULL disables the feature. */ + RGFW_key exitKey; + i32 lastMouseX, lastMouseY; /*!< last cusor point (for raw mouse data) */ + + RGFW_bool shouldClose; + RGFW_bool rawMouse; + RGFW_bool captureMouse; + RGFW_bool inFocus; + RGFW_bool mouseInside; + RGFW_keymod mod; + RGFW_eventFlag enabledEvents; + u32 flags; /*!< windows flags (for RGFW to check and modify) */ + i32 oldX, oldY, oldW, oldH; +} RGFW_windowInternal; + +struct RGFW_window { + RGFW_window_src src; /*!< src window data */ + RGFW_windowInternal internal; /*!< internal window data that is not specific to the OS */ + void* userPtr; /* ptr for user data */ + i32 x, y, w, h; /*!< position and size of the window */ +}; /*!< window structure for the window */ + +typedef struct RGFW_windowState { + RGFW_bool mouseEnter; + RGFW_bool dataDragging; + RGFW_bool dataDrop; + size_t filesCount; + i32 dropX, dropY; + RGFW_window* win; /*!< it's not possible for one of these events to happen in the frame that the other event happened */ + + RGFW_bool mouseLeave; + RGFW_window* winLeave; /*!< if a mouse leaves one window and enters the next */ +} RGFW_windowState; + +typedef struct { + RGFW_bool current; + RGFW_bool prev; +} RGFW_keyState; + +struct RGFW_monitorNode { + RGFW_monitor mon; + RGFW_bool disconnected; + RGFW_monitorNode* next; +#ifdef RGFW_WAYLAND + u32 id; /* Add id so wl_outputs can be removed */ + struct wl_output *output; + struct zxdg_output_v1 *xdg_output; + RGFW_monitorMode* modes; + size_t modeCount; +#endif +#if defined(RGFW_X11) && !defined(RGFW_NO_XRANDR) + i32 screen; + RROutput rrOutput; + RRCrtc crtc; +#endif +#ifdef RGFW_WINDOWS + HMONITOR hMonitor; + WCHAR adapterName[32]; + WCHAR deviceName[32]; +#endif +#ifdef RGFW_MACOS + void* screen; + CGDirectDisplayID display; + u32 uintNum; +#endif +}; + +typedef struct RGFW_monitorList { + RGFW_monitorNode* head; + RGFW_monitorNode* cur; +} RGFW_monitorList; + +typedef struct RGFW_monitors { + RGFW_monitorList list; + RGFW_monitorList freeList; + size_t count; + + RGFW_monitorNode* primary; + RGFW_monitorNode data[RGFW_MAX_MONITORS]; +} RGFW_monitors; + +RGFWDEF RGFW_monitorNode* RGFW_monitors_add(const RGFW_monitor* mon); +RGFWDEF void RGFW_monitors_remove(RGFW_monitorNode* node, RGFW_monitorNode* prev); + +struct RGFW_info { + RGFW_window* root; + i32 windowCount; + + RGFW_mouse* hiddenMouse; + + RGFW_event events[RGFW_MAX_EVENTS]; /* A circular buffer (FIFO), using eventBottom/Len */ + + i32 eventBottom; + i32 eventLen; + RGFW_bool queueEvents; + RGFW_bool polledEvents; + + u32 apiKeycodes[RGFW_keyLast]; + #if defined(RGFW_X11) || defined(RGFW_WAYLAND) + u8 keycodes[256]; + #elif defined(RGFW_WINDOWS) + u8 keycodes[512]; + #elif defined(RGFW_MACOS) + u8 keycodes[128]; + #elif defined(RGFW_WASM) + u8 keycodes[256]; + #endif + + const char* className; + RGFW_bool useWaylandBool; + RGFW_bool stopCheckEvents_bool ; + u64 timerOffset; + + char* clipboard_data; + char* clipboard; /* for writing to the clipboard selection */ + size_t clipboard_len; + char filesSrc[RGFW_MAX_PATH * RGFW_MAX_DROPS]; + char** files; + #ifdef RGFW_X11 + Display* display; + XContext context; + Window helperWindow; + const char* instName; + XErrorEvent* x11Error; + i32 xrandrEventBase; + #endif + #ifdef RGFW_WAYLAND + struct wl_display* wl_display; + struct xkb_context *xkb_context; + struct xkb_keymap *keymap; + struct xkb_state *xkb_state; + struct zxdg_decoration_manager_v1 *decoration_manager; + struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; + struct zwp_relative_pointer_v1 *relative_pointer; + struct zwp_pointer_constraints_v1 *constraint_manager; + struct xdg_toplevel_icon_manager_v1 *icon_manager; + + struct zxdg_output_manager_v1 *xdg_output_manager; + + struct wl_data_device_manager *data_device_manager; + struct wl_data_device *data_device; // supports clipboard and DND + + struct wl_keyboard* wl_keyboard; + struct wl_pointer* wl_pointer; + struct wl_compositor* compositor; + struct xdg_wm_base* xdg_wm_base; + struct wl_shm* shm; + struct wl_seat *seat; + struct wl_registry *registry; + u32 mouse_enter_serial; + struct wl_cursor_theme* wl_cursor_theme; + struct wl_surface* cursor_surface; + + RGFW_window* kbOwner; + RGFW_window* mouseOwner; /* what window has access to the mouse */ + + #endif + + RGFW_monitors monitors; + + #ifdef RGFW_UNIX + int eventWait_forceStop[3]; + i32 clock; + #endif + + #ifdef RGFW_MACOS + void* NSApp; + i64 flash; + void* customViewClasses[2]; /* NSView and NSOpenGLView */ + void* customNSAppDelegateClass; + void* customWindowDelegateClass; + void* customNSAppDelegate; + #endif + + #ifdef RGFW_OPENGL + RGFW_window* current; + #endif + #ifdef RGFW_EGL + void* EGL_display; + #endif + + RGFW_bool rawMouse; /* global raw mouse toggle */ + + RGFW_windowState windowState; /*! for checking window state events */ + + RGFW_keyState mouseButtons[RGFW_mouseFinal]; + RGFW_keyState keyboard[RGFW_keyLast]; + float scrollX, scrollY; + float vectorX, vectorY; +}; +#endif /* RGFW_NATIVE_HEADER */ + +#ifdef RGFW_IMPLEMENTATION + +#ifndef RGFW_NO_MATH +#include +#endif + +/* global private API */ + +/* for C++ / C89 */ +RGFWDEF RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win); +RGFWDEF void RGFW_window_closePlatform(RGFW_window* win); + +RGFWDEF void RGFW_window_setFlagsInternal(RGFW_window* win, RGFW_windowFlags flags, RGFW_windowFlags cmpFlags); + +RGFWDEF void RGFW_initKeycodes(void); +RGFWDEF void RGFW_initKeycodesPlatform(void); +RGFWDEF void RGFW_resetPrevState(void); +RGFWDEF void RGFW_resetKey(void); +RGFWDEF void RGFW_unloadEGL(void); +RGFWDEF void RGFW_updateKeyModsEx(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll); +RGFWDEF void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll); +RGFWDEF void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show); +RGFWDEF void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value); +RGFWDEF void RGFW_monitors_refresh(void); + +RGFWDEF void RGFW_windowMaximizedCallback(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); +RGFWDEF void RGFW_windowMinimizedCallback(RGFW_window* win); +RGFWDEF void RGFW_windowRestoredCallback(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); +RGFWDEF void RGFW_windowMovedCallback(RGFW_window* win, i32 x, i32 y); +RGFWDEF void RGFW_windowResizedCallback(RGFW_window* win, i32 w, i32 h); +RGFWDEF void RGFW_windowQuitCallback(RGFW_window* win); +RGFWDEF void RGFW_mousePosCallback(RGFW_window* win, i32 x, i32 y, float vecX, float vecY); +RGFWDEF void RGFW_windowRefreshCallback(RGFW_window* win); +RGFWDEF void RGFW_focusCallback(RGFW_window* win, RGFW_bool inFocus); +RGFWDEF void RGFW_mouseNotifyCallback(RGFW_window* win, i32 x, i32 y, RGFW_bool status); +RGFWDEF void RGFW_dataDropCallback(RGFW_window* win, char** files, size_t count); +RGFWDEF void RGFW_dataDragCallback(RGFW_window* win, i32 x, i32 y); +RGFWDEF void RGFW_keyCallback(RGFW_window* win, RGFW_key key, RGFW_key sym, RGFW_keymod mod, RGFW_bool repeat, RGFW_bool press); +RGFWDEF void RGFW_mouseButtonCallback(RGFW_window* win, RGFW_mouseButton button, RGFW_bool press); +RGFWDEF void RGFW_mouseScrollCallback(RGFW_window* win, float x, float y); +RGFWDEF void RGFW_scaleUpdatedCallback(RGFW_window* win, float scaleX, float scaleY); +RGFWDEF void RGFW_monitorCallback(RGFW_window* win, const RGFW_monitor* monitor, RGFW_bool connected); + +RGFWDEF void RGFW_setBit(u32* var, u32 mask, RGFW_bool set); +RGFWDEF void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode); + +RGFWDEF void RGFW_window_captureMousePlatform(RGFW_window* win, RGFW_bool state); +RGFWDEF void RGFW_window_setRawMouseModePlatform(RGFW_window *win, RGFW_bool state); + +RGFWDEF void RGFW_copyImageData64(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, + u8* src_data, RGFW_format src_format, RGFW_bool is64bit); + +RGFWDEF RGFW_bool RGFW_loadEGL(void); + +#ifdef RGFW_OPENGL +typedef struct RGFW_attribStack { + i32* attribs; + size_t count; + size_t max; +} RGFW_attribStack; +RGFWDEF void RGFW_attribStack_init(RGFW_attribStack* stack, i32* attribs, size_t max); +RGFWDEF void RGFW_attribStack_pushAttrib(RGFW_attribStack* stack, i32 attrib); +RGFWDEF void RGFW_attribStack_pushAttribs(RGFW_attribStack* stack, i32 attrib1, i32 attrib2); + +RGFWDEF RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, size_t len); +#endif + +typedef struct RGFW_colorLayout { i32 r, g, b, a; } RGFW_colorLayout; + +#ifdef RGFW_X11 +RGFWDEF void RGFW_XCreateWindow (XVisualInfo visual, const char* name, RGFW_windowFlags flags, RGFW_window* win); +#endif +#ifdef RGFW_MACOS +RGFWDEF void RGFW_osx_initView(RGFW_window* win); +#endif +/* end of global private API defs */ + +RGFW_info* _RGFW = NULL; +void RGFW_setInfo(RGFW_info* info) { _RGFW = info; } +RGFW_info* RGFW_getInfo(void) { return _RGFW; } + + +void* RGFW_alloc(size_t size) { return RGFW_ALLOC(size); } +void RGFW_free(void* ptr) { RGFW_FREE(ptr); } + +void RGFW_useWayland(RGFW_bool wayland) { RGFW_init(); _RGFW->useWaylandBool = RGFW_BOOL(wayland); } +RGFW_bool RGFW_usingWayland(void) { return _RGFW->useWaylandBool; } + +void RGFW_setRawMouseMode(RGFW_bool state) { + _RGFW->rawMouse = state; + RGFW_window_setRawMouseModePlatform(_RGFW->root, state); +} + +void RGFW_clipboard_switch(char* newstr); +void RGFW_clipboard_switch(char* newstr) { + if (_RGFW->clipboard_data != NULL) + RGFW_FREE(_RGFW->clipboard_data); + _RGFW->clipboard_data = newstr; +} + +#define RGFW_CHECK_CLIPBOARD() \ + if (size <= 0 && _RGFW->clipboard_data != NULL) \ + return (const char*)_RGFW->clipboard_data; \ + else if (size <= 0) \ + return "\0"; + +const char* RGFW_readClipboard(size_t* len) { + RGFW_ssize_t size = RGFW_readClipboardPtr(NULL, 0); + RGFW_CHECK_CLIPBOARD(); + char* str = (char*)RGFW_ALLOC((size_t)size); + RGFW_ASSERT(str != NULL); + str[0] = '\0'; + + size = RGFW_readClipboardPtr(str, (size_t)size); + + RGFW_CHECK_CLIPBOARD(); + + if (len != NULL) *len = (size_t)size; + + RGFW_clipboard_switch(str); + return (const char*)str; +} + +/* +RGFW_IMPLEMENTATION starts with generic RGFW defines + +This is the start of keycode data +*/ + + + +void RGFW_initKeycodes(void) { + RGFW_MEMSET(_RGFW->keycodes, 0, sizeof(_RGFW->keycodes)); + RGFW_initKeycodesPlatform(); + u32 i, y; + for (i = 0; i < RGFW_keyLast; i++) { + for (y = 0; y < sizeof(_RGFW->keycodes); y++) { + if (_RGFW->keycodes[y] == i) { + _RGFW->apiKeycodes[i] = y; + break; + } + } + } + + + RGFW_resetKey(); +} + +u32 RGFW_apiKeyToRGFW(u32 keycode) { + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(_RGFW->keycodes) / sizeof(u8)) + return 0; + + return _RGFW->keycodes[keycode]; +} + +u32 RGFW_rgfwToApiKey(u32 keycode) { + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(_RGFW->apiKeycodes) / sizeof(u32)) + return 0; + + return _RGFW->apiKeycodes[keycode]; +} + +void RGFW_resetKey(void) { RGFW_MEMSET(_RGFW->keyboard, 0, sizeof(_RGFW->keyboard)); } +/* + this is the end of keycode data +*/ + +/* + event callback defines start here +*/ + + +/* + These exist to avoid the + if (func == NULL) check + for (allegedly) better performance + + RGFW_EMPTY_DEF exists to prevent the missing-prototypes warning +*/ +#define RGFW_CALLBACK_DEFINE(x, x2) \ +RGFW_##x##func RGFW_##x##CallbackSrc = NULL; \ +RGFW_##x##func RGFW_set##x2##Callback(RGFW_##x##func func) { \ + RGFW_##x##func prev = RGFW_##x##CallbackSrc; \ + RGFW_##x##CallbackSrc = func; \ + return prev; \ +} + +RGFW_CALLBACK_DEFINE(windowMaximized, WindowMaximized) +RGFW_CALLBACK_DEFINE(windowMinimized, WindowMinimized) +RGFW_CALLBACK_DEFINE(windowRestored, WindowRestored) +RGFW_CALLBACK_DEFINE(windowMoved, WindowMoved) +RGFW_CALLBACK_DEFINE(windowResized, WindowResized) +RGFW_CALLBACK_DEFINE(windowQuit, WindowQuit) +RGFW_CALLBACK_DEFINE(mousePos, MousePos) +RGFW_CALLBACK_DEFINE(windowRefresh, WindowRefresh) +RGFW_CALLBACK_DEFINE(focus, Focus) +RGFW_CALLBACK_DEFINE(mouseNotify, MouseNotify) +RGFW_CALLBACK_DEFINE(dataDrop, DataDrop) +RGFW_CALLBACK_DEFINE(dataDrag, DataDrag) +RGFW_CALLBACK_DEFINE(key, Key) +RGFW_CALLBACK_DEFINE(mouseButton, MouseButton) +RGFW_CALLBACK_DEFINE(mouseScroll, MouseScroll) +RGFW_CALLBACK_DEFINE(scaleUpdated, ScaleUpdated) +RGFW_CALLBACK_DEFINE(monitor, Monitor) +RGFW_CALLBACK_DEFINE(debug, Debug) +#define RGFW_debugCallback(type, err, msg) if (RGFW_debugCallbackSrc) RGFW_debugCallbackSrc(type, err, msg); +#undef RGFW_CALLBACK_DEFINE + +void RGFW_windowMaximizedCallback(RGFW_window* win, i32 x, i32 y, i32 w, i32 h) { + win->internal.flags |= RGFW_windowMaximize; + + if (!(win->internal.enabledEvents & RGFW_windowMaximizedFlag)) return; + + RGFW_event event; + event.type = RGFW_windowMaximized; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_windowMaximizedCallbackSrc) RGFW_windowMaximizedCallbackSrc(win, x, y, w, h); +} + +void RGFW_windowMinimizedCallback(RGFW_window* win) { + win->internal.flags |= RGFW_windowMinimize; + + if (!(win->internal.enabledEvents & RGFW_windowMinimizedFlag)) return; + + RGFW_event event; + event.type = RGFW_windowMinimized; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_windowMinimizedCallbackSrc) RGFW_windowMinimizedCallbackSrc(win); +} + +void RGFW_windowRestoredCallback(RGFW_window* win, i32 x, i32 y, i32 w, i32 h) { + win->internal.flags &= ~(u32)RGFW_windowMinimize; + if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->internal.flags &= ~(u32)RGFW_windowMaximize; + + if (!(win->internal.enabledEvents & RGFW_windowRestoredFlag)) return; + + RGFW_event event; + event.type = RGFW_windowRestored; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_windowRestoredCallbackSrc) RGFW_windowRestoredCallbackSrc(win, x, y, w, h); +} + +void RGFW_windowMovedCallback(RGFW_window* win, i32 x, i32 y) { + win->x = x; + win->y = y; + if (!(win->internal.enabledEvents & RGFW_windowMovedFlag)) return; + + RGFW_event event; + event.type = RGFW_windowMoved; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_windowMovedCallbackSrc) RGFW_windowMovedCallbackSrc(win, x, y); +} + +void RGFW_windowResizedCallback(RGFW_window* win, i32 w, i32 h) { + win->w = w; + win->h = h; + + if (!(win->internal.enabledEvents & RGFW_windowResizedFlag)) return; + RGFW_event event; + event.type = RGFW_windowResized; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_windowResizedCallbackSrc) RGFW_windowResizedCallbackSrc(win, w, h); +} + +void RGFW_windowQuitCallback(RGFW_window* win) { + win->internal.shouldClose = RGFW_TRUE; + + RGFW_event event; + event.type = RGFW_quit; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_windowQuitCallbackSrc) RGFW_windowQuitCallbackSrc(win); +} + +void RGFW_mousePosCallback(RGFW_window* win, i32 x, i32 y, float vecX, float vecY) { + win->internal.lastMouseX = x; + win->internal.lastMouseY = y; + _RGFW->vectorX = vecX; + _RGFW->vectorY = vecY; + + if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag)) return; + + RGFW_event event; + event.type = RGFW_mousePosChanged; + event.mouse.x = x; + event.mouse.y = y; + event.mouse.vecX = vecX; + event.mouse.vecY = vecY; + event.common.win = win; + + RGFW_eventQueuePush(&event); + + if (RGFW_mousePosCallbackSrc) RGFW_mousePosCallbackSrc(win, x, y, vecX, vecY); +} + +void RGFW_windowRefreshCallback(RGFW_window* win) { + if (!(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return; + RGFW_event event; + event.type = RGFW_windowRefresh; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_windowRefreshCallbackSrc) RGFW_windowRefreshCallbackSrc(win); +} + +void RGFW_focusCallback(RGFW_window* win, RGFW_bool inFocus) { + win->internal.inFocus = inFocus; + + if (win->internal.captureMouse) { + RGFW_window_captureMousePlatform(win, inFocus); + } + + RGFW_event event; + event.common.win = win; + + if (inFocus == RGFW_TRUE) { + if ((win->internal.flags & RGFW_windowFullscreen)) + RGFW_window_raise(win); + + event.type = RGFW_focusIn; + } else if (inFocus == RGFW_FALSE) { + if ((win->internal.flags & RGFW_windowFullscreen)) + RGFW_window_minimize(win); + + size_t key; + for (key = 0; key < RGFW_keyLast; key++) { + if (RGFW_isKeyDown((u8)key) == RGFW_FALSE) continue; + + _RGFW->keyboard[key].current = RGFW_FALSE; + u8 sym = RGFW_rgfwToKeyChar((u32)key); + + if ((win->internal.enabledEvents & RGFW_BIT(RGFW_keyReleased))) { + RGFW_keyCallback(win, (u8)key, sym, win->internal.mod, RGFW_FALSE, RGFW_FALSE); + } + } + + RGFW_resetKey(); + event.type = RGFW_focusOut; + } + + event.common.win = win; + + RGFW_eventQueuePush(&event); + + if (RGFW_focusCallbackSrc) RGFW_focusCallbackSrc(win, inFocus); +} + +void RGFW_mouseNotifyCallback(RGFW_window* win, i32 x, i32 y, RGFW_bool status) { + win->internal.mouseInside = status; + _RGFW->windowState.win = win; + + win->internal.lastMouseX = x; + win->internal.lastMouseY = y; + + RGFW_event event; + event.common.win = win; + event.mouse.x = x; + event.mouse.y = y; + + if (status) { + if (!(win->internal.enabledEvents & RGFW_mouseEnterFlag)) return; + _RGFW->windowState.mouseEnter = RGFW_TRUE; + _RGFW->windowState.win = win; + event.type = RGFW_mouseEnter; + } else { + if (!(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return; + _RGFW->windowState.winLeave = win; + _RGFW->windowState.mouseLeave = RGFW_TRUE; + event.type = RGFW_mouseLeave; + } + + RGFW_eventQueuePush(&event); + + if (RGFW_mouseNotifyCallbackSrc) RGFW_mouseNotifyCallbackSrc(win, x, y, status); +} + +void RGFW_dataDropCallback(RGFW_window* win, char** files, size_t count) { + if (!(win->internal.enabledEvents & RGFW_dataDropFlag) || !(win->internal.flags & RGFW_windowAllowDND)) + return; + + _RGFW->windowState.win = win; + _RGFW->windowState.dataDrop = RGFW_TRUE; + _RGFW->windowState.filesCount = count; + + RGFW_event event; + event.type = RGFW_dataDrop; + event.drop.files = files; + event.drop.count = count; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_dataDropCallbackSrc) RGFW_dataDropCallbackSrc(win, files, count); +} + +void RGFW_dataDragCallback(RGFW_window* win, i32 x, i32 y) { + _RGFW->windowState.win = win; + _RGFW->windowState.dataDragging = RGFW_TRUE; + _RGFW->windowState.dropX = x; + _RGFW->windowState.dropY = y; + + if (win->internal.enabledEvents & RGFW_dataDragFlag) return; + + RGFW_event event; + event.type = RGFW_dataDrag; + event.drag.x = x; + event.drag.y = y; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_dataDragCallbackSrc) RGFW_dataDragCallbackSrc(win, x, y); +} + +void RGFW_keyCallback(RGFW_window* win, RGFW_key key, RGFW_key sym, RGFW_keymod mod, RGFW_bool repeat, RGFW_bool press) { + RGFW_event event; + + if (press) { + if (!(win->internal.enabledEvents & RGFW_keyPressedFlag)) return; + event.type = RGFW_keyPressed; + } else { + if (!(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return; + event.type = RGFW_keyReleased; + } + + event.key.value = key; + event.key.sym = sym; + event.key.mod = repeat; + event.key.mod = mod; + event.common.win = win; + RGFW_eventQueuePush(&event); + + _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; + _RGFW->keyboard[event.key.value].current = press; + + if (RGFW_keyCallbackSrc) RGFW_keyCallbackSrc(win, key, sym, mod, repeat, press); +} + +void RGFW_mouseButtonCallback(RGFW_window* win, RGFW_mouseButton button, RGFW_bool press) { + RGFW_event event; + if (press) { + if (!(win->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return; + event.type = RGFW_mouseButtonPressed; + } else { + if (!(win->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return; + event.type = RGFW_mouseButtonReleased; + } + + _RGFW->mouseButtons[button].prev = _RGFW->mouseButtons[button].current; + _RGFW->mouseButtons[button].current = press; + + event.button.value = button; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_mouseButtonCallbackSrc) RGFW_mouseButtonCallbackSrc(win, button, press); +} + +void RGFW_mouseScrollCallback(RGFW_window* win, float x, float y) { + if (!(win->internal.enabledEvents & RGFW_mouseScrollFlag)) return; + _RGFW->scrollX = x; + _RGFW->scrollY = y; + + RGFW_event event; + event.type = RGFW_mouseScroll; + event.scroll.x = x; + event.scroll.y = y; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_mouseScrollCallbackSrc) RGFW_mouseScrollCallbackSrc(win, x, y); +} + +void RGFW_scaleUpdatedCallback(RGFW_window* win, float scaleX, float scaleY) { + if (win->internal.flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); + if (!(win->internal.enabledEvents & RGFW_scaleUpdatedFlag)) return; + + RGFW_event event; + event.type = RGFW_scaleUpdated; + event.scale.x = scaleX; + event.scale.y = scaleY; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_scaleUpdatedCallbackSrc) RGFW_scaleUpdatedCallbackSrc(win, scaleX, scaleY); +} + +void RGFW_monitorCallback(RGFW_window* win, const RGFW_monitor* monitor, RGFW_bool connected) { + if (win) { + if (connected && !(win->internal.enabledEvents & RGFW_monitorConnectedFlag)) return; + if (!connected && !(win->internal.enabledEvents & RGFW_monitorDisconnectedFlag)) return; + } + + RGFW_event event; + event.type = (connected) ? (RGFW_monitorConnected) : (RGFW_monitorDisconnected); + event.monitor.monitor = monitor; + event.common.win = win; + RGFW_eventQueuePush(&event); + + if (RGFW_monitorCallbackSrc) RGFW_monitorCallbackSrc(win, monitor, connected); +} + +#ifdef RGFW_DEBUG +#include +#endif + +void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, const char* msg) { + RGFW_debugCallback(type, err, msg); + + #ifdef RGFW_DEBUG + switch (type) { + case RGFW_typeInfo: RGFW_PRINTF("RGFW INFO (%i %i): %s", type, err, msg); break; + case RGFW_typeError: RGFW_PRINTF("RGFW DEBUG (%i %i): %s", type, err, msg); break; + case RGFW_typeWarning: RGFW_PRINTF("RGFW WARNING (%i %i): %s", type, err, msg); break; + default: break; + } + + RGFW_PRINTF("\n"); + #endif +} + +void RGFW_window_checkMode(RGFW_window* win); +void RGFW_window_checkMode(RGFW_window* win) { + if (RGFW_window_isMinimized(win) && (win->internal.enabledEvents & RGFW_windowMinimizedFlag)) { + RGFW_windowMinimizedCallback(win); + } else if (RGFW_window_isMaximized(win) && (win->internal.enabledEvents & RGFW_windowMaximizedFlag)) { + RGFW_windowMaximizedCallback(win, win->x, win->y, win->w, win->h); + } else if ((((win->internal.flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || + (win->internal.flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) && (win->internal.enabledEvents & RGFW_windowRestoredFlag)) { + RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); + } +} + +/* +no more event call back defines +*/ + +size_t RGFW_sizeofInfo(void) { return sizeof(RGFW_info); } +size_t RGFW_sizeofNativeImage(void) { return sizeof(RGFW_nativeImage); } +size_t RGFW_sizeofSurface(void) { return sizeof(RGFW_surface); } +size_t RGFW_sizeofWindow(void) { return sizeof(RGFW_window); } +size_t RGFW_sizeofWindowSrc(void) { return sizeof(RGFW_window_src); } + +RGFW_window_src* RGFW_window_getSrc(RGFW_window* win) { return &win->src; } +RGFW_bool RGFW_window_getPosition(RGFW_window* win, i32* x, i32* y) { if (x) *x = win->x; if (y) *y = win->y; return RGFW_TRUE; } +RGFW_bool RGFW_window_getSize(RGFW_window* win, i32* w, i32* h) { if (w) *w = win->w; if (h) *h = win->h; return RGFW_TRUE; } +u32 RGFW_window_getFlags(RGFW_window* win) { return win->internal.flags; } +RGFW_key RGFW_window_getExitKey(RGFW_window* win) { return win->internal.exitKey; } +void RGFW_window_setExitKey(RGFW_window* win, RGFW_key key) { win->internal.exitKey = key; } +void RGFW_window_setEnabledEvents(RGFW_window* win, RGFW_eventFlag events) { win->internal.enabledEvents = events; } +RGFW_eventFlag RGFW_window_getEnabledEvents(RGFW_window* win) { return win->internal.enabledEvents; } +void RGFW_window_setDisabledEvents(RGFW_window* win, RGFW_eventFlag events) { RGFW_window_setEnabledEvents(win, (RGFW_allEventFlags) & ~(u32)events); } +void RGFW_window_setEventState(RGFW_window* win, RGFW_eventFlag event, RGFW_bool state) { RGFW_setBit(&win->internal.enabledEvents, event, state); } +void* RGFW_window_getUserPtr(RGFW_window* win) { return win->userPtr; } +void RGFW_window_setUserPtr(RGFW_window* win, void* ptr) { win->userPtr = ptr; } + +RGFW_bool RGFW_window_getSizeInPixels(RGFW_window* win, i32* w, i32* h) { + RGFW_monitor* mon = RGFW_window_getMonitor(win); + if (mon == NULL) return RGFW_FALSE; + + if (w) *w = (i32)((float)win->w * mon->pixelRatio); + if (h) *h = (i32)((float)win->h * mon->pixelRatio); + + return RGFW_TRUE; +} + + +#if defined(RGFW_USE_XDL) && defined(RGFW_X11) + #define XDL_IMPLEMENTATION + #include "XDL.h" +#endif + +#ifndef RGFW_FORCE_INIT +RGFW_info _rgfwGlobal; +#endif + +i32 RGFW_init(void) { return RGFW_init_ptr(&_rgfwGlobal); } +void RGFW_deinit(void) { RGFW_deinit_ptr(&_rgfwGlobal); } + +i32 RGFW_initPlatform(void); +void RGFW_deinitPlatform(void); + +i32 RGFW_init_ptr(RGFW_info* info) { + if (info == _RGFW || info == NULL) return 1; + + RGFW_setInfo(info); + RGFW_MEMSET(_RGFW, 0, sizeof(RGFW_info)); + _RGFW->queueEvents = RGFW_FALSE; + _RGFW->polledEvents = RGFW_FALSE; +#ifdef RGFW_WAYLAND + _RGFW->useWaylandBool = RGFW_TRUE; +#endif + + _RGFW->files = (char**)(void*)_RGFW->filesSrc; + u32 i; + for (i = 0; i < RGFW_MAX_DROPS; i++) + _RGFW->files[i] = (char*)(_RGFW->filesSrc + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); + + _RGFW->monitors.freeList.head = &_RGFW->monitors.data[0]; + _RGFW->monitors.freeList.cur = _RGFW->monitors.freeList.head; + + for (i = 1; i < RGFW_MAX_MONITORS; i++) { + RGFW_monitorNode* newNode = &_RGFW->monitors.data[i]; + _RGFW->monitors.freeList.cur->next = newNode; + _RGFW->monitors.freeList.cur = _RGFW->monitors.freeList.cur->next; + } + + _RGFW->monitors.list.head = NULL; + _RGFW->monitors.list.head = NULL; + RGFW_initKeycodes(); + i32 out = RGFW_initPlatform(); + + RGFW_pollMonitors(); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context initialized"); + + return out; +} + +#ifndef RGFW_EGL +void RGFW_unloadEGL(void) { } +#endif + +void RGFW_deinit_ptr(RGFW_info* info) { + if (info == NULL) return; + + RGFW_setInfo(info); + RGFW_unloadEGL(); + RGFW_deinitPlatform(); + + _RGFW->root = NULL; + _RGFW->windowCount = 0; + RGFW_setInfo(NULL); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context deinitialized"); +} + +RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags) { + RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); + RGFW_ASSERT(win != NULL); + return RGFW_createWindowPtr(name, x, y, w, h, flags, win); +} + +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_window_closePtr(win); + RGFW_FREE(win); +} + +RGFW_window* RGFW_createWindowPtr(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if (name == NULL) name = "\0"; + + RGFW_MEMSET(win, 0, sizeof(RGFW_window)); + + if (_RGFW == NULL) RGFW_init(); + _RGFW->windowCount++; + + /* rect based the requested flags */ + if (_RGFW->root == NULL) { + RGFW_setRootWindow(win); + } + + /* set and init the new window's data */ + win->x = x; + win->y = y; + win->w = w; + win->h = h; + win->internal.flags = flags; + win->internal.enabledEvents = RGFW_allEventFlags; + + RGFW_window* ret = RGFW_createWindowPlatform(name, flags, win); + +#ifndef RGFW_X11 + RGFW_window_setFlagsInternal(win, flags, 0); +#endif + +#ifdef RGFW_OPENGL + win->src.gfxType = 0; + if (flags & RGFW_windowOpenGL) + RGFW_window_createContext_OpenGL(win, RGFW_getGlobalHints_OpenGL()); +#endif + +#ifdef RGFW_EGL + if (flags & RGFW_windowEGL) + RGFW_window_createContext_EGL(win, RGFW_getGlobalHints_OpenGL()); +#endif + + /* X11 creates the window after the OpenGL context is created (because of visual garbage), + * so we have to wait to set the flags + * This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used + * if a window is created, CreateContext will delete the window and create a new one + * */ +#ifdef RGFW_X11 + RGFW_window_setFlagsInternal(win, flags, 0); +#endif + +#ifdef RGFW_MACOS + /*NOTE: another OpenGL/setFlags related hack, this because OSX the 'view' class must be setup after the NSOpenGL view is made AND after setFlags happens */ + RGFW_osx_initView(win); +#endif + +#ifdef RGFW_WAYLAND + /* recieve all events needed to configure the surface */ + /* also gets the wl_outputs */ + if (RGFW_usingWayland()) { + wl_display_roundtrip(_RGFW->wl_display); + /* NOTE: this is a hack so that way wayland spawns a window, even if nothing is drawn */ + if (!(flags & RGFW_windowOpenGL) && !(flags & RGFW_windowEGL)) { + u8* data = (u8*)RGFW_ALLOC((u32)(win->w * win->h * 3)); + RGFW_MEMSET(data, 0, (u32)(win->w * win->h * 3) * sizeof(u8)); + RGFW_surface* surface = RGFW_createSurface(data, win->w, win->h, RGFW_formatBGR8); + RGFW_window_blitSurface(win, surface); + RGFW_FREE(data); + RGFW_surface_free(surface); + } + } +#endif + + if (!(flags & RGFW_windowHideMouse)) { + RGFW_window_setMouseDefault(win); + } + + RGFW_window_setName(win, name); + if (!(flags & RGFW_windowHide)) { + flags |= RGFW_windowHide; + RGFW_window_show(win); + } + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a new window was created"); + + return ret; +} + +void RGFW_window_closePtr(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + if (win->internal.captureMouse) { + RGFW_window_captureMouse(win, RGFW_FALSE); + } + + #ifdef RGFW_EGL + if ((win->src.gfxType & RGFW_gfxEGL) && win->src.ctx.egl) { + RGFW_window_deleteContext_EGL(win, win->src.ctx.egl); + win->src.ctx.egl = NULL; + } + #endif + + #ifdef RGFW_OPENGL + if ((win->src.gfxType & RGFW_gfxNativeOpenGL) && win->src.ctx.native) { + RGFW_window_deleteContext_OpenGL(win, win->src.ctx.native); + win->src.ctx.native = NULL; + } + #endif + + RGFW_window_closePlatform(win); + + RGFW_clipboard_switch(NULL); + + _RGFW->windowCount--; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a window was freed"); + + if (_RGFW->windowCount == 0) RGFW_deinit(); +} + +void RGFW_setQueueEvents(RGFW_bool queue) { _RGFW->queueEvents = RGFW_BOOL(queue); } + +void RGFW_eventQueueFlush(void) { _RGFW->eventLen = 0; } + +void RGFW_eventQueuePush(const RGFW_event* event) { + if (_RGFW->queueEvents == RGFW_FALSE) return; + RGFW_ASSERT(_RGFW->eventLen >= 0); + + if (_RGFW->eventLen >= RGFW_MAX_EVENTS) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEventQueue, "Event queue limit 'RGFW_MAX_EVENTS' has been reached automatically flushing queue."); + RGFW_eventQueueFlush(); + return; + } + + i32 eventTop = (_RGFW->eventBottom + _RGFW->eventLen) % RGFW_MAX_EVENTS; + _RGFW->eventLen += 1; + _RGFW->events[eventTop] = *event; +} + +RGFW_event* RGFW_eventQueuePop(void) { + RGFW_ASSERT(_RGFW->eventLen >= 0 && _RGFW->eventLen <= RGFW_MAX_EVENTS); + RGFW_event* ev; + + if (_RGFW->eventLen == 0) { + return NULL; + } + + ev = &_RGFW->events[_RGFW->eventBottom]; + _RGFW->eventLen -= 1; + _RGFW->eventBottom = (_RGFW->eventBottom + 1) % RGFW_MAX_EVENTS; + + return ev; +} + +RGFW_bool RGFW_checkEvent(RGFW_event* event) { + if (_RGFW->eventLen == 0 && _RGFW->polledEvents == RGFW_FALSE) { + _RGFW->queueEvents = RGFW_TRUE; + RGFW_pollEvents(); + _RGFW->polledEvents = RGFW_TRUE; + } + + if (RGFW_checkQueuedEvent(event) == RGFW_FALSE) { + _RGFW->polledEvents = RGFW_FALSE; + return RGFW_FALSE; + } + + return RGFW_TRUE; +} + +RGFW_bool RGFW_checkQueuedEvent(RGFW_event* event) { + RGFW_event* ev; + _RGFW->queueEvents = RGFW_TRUE; + /* check queued events */ + ev = RGFW_eventQueuePop(); + if (ev != NULL) { + *event = *ev; + return RGFW_TRUE; + } + + return RGFW_FALSE; +} + +void RGFW_resetPrevState(void) { + size_t i; /*!< reset each previous state */ + for (i = 0; i < RGFW_keyLast; i++) _RGFW->keyboard[i].prev = _RGFW->keyboard[i].current; + for (i = 0; i < RGFW_mouseFinal; i++) _RGFW->mouseButtons[i].prev = _RGFW->mouseButtons[i].current; + _RGFW->scrollX = 0.0f; + _RGFW->scrollY = 0.0f; + _RGFW->vectorX = (float)0.0f; + _RGFW->vectorY = (float)0.0f; + RGFW_MEMSET(&_RGFW->windowState, 0, sizeof(_RGFW->windowState)); +} + +RGFW_bool RGFW_isKeyPressed(RGFW_key key) { + RGFW_ASSERT(_RGFW != NULL); + return _RGFW->keyboard[key].current && !_RGFW->keyboard[key].prev; +} +RGFW_bool RGFW_isKeyDown(RGFW_key key) { + RGFW_ASSERT(_RGFW != NULL); + return _RGFW->keyboard[key].current; +} +RGFW_bool RGFW_isKeyReleased(RGFW_key key) { + RGFW_ASSERT(_RGFW != NULL); + return !_RGFW->keyboard[key].current && _RGFW->keyboard[key].prev; +} + + +RGFW_bool RGFW_isMousePressed(RGFW_mouseButton button) { + RGFW_ASSERT(_RGFW != NULL); + return _RGFW->mouseButtons[button].current && !_RGFW->mouseButtons[button].prev; +} +RGFW_bool RGFW_isMouseDown(RGFW_mouseButton button) { + RGFW_ASSERT(_RGFW != NULL); + return _RGFW->mouseButtons[button].current; +} +RGFW_bool RGFW_isMouseReleased(RGFW_mouseButton button) { + RGFW_ASSERT(_RGFW != NULL); + return !_RGFW->mouseButtons[button].current && _RGFW->mouseButtons[button].prev; +} + +void RGFW_getMouseScroll(float* x, float* y) { + RGFW_ASSERT(_RGFW != NULL); + if (x) *x = _RGFW->scrollX; + if (y) *y = _RGFW->scrollY; +} + +void RGFW_getMouseVector(float* x, float* y) { + RGFW_ASSERT(_RGFW != NULL); + if (x) *x = _RGFW->vectorX; + if (y) *y = _RGFW->vectorY; +} + +RGFW_bool RGFW_window_didMouseLeave(RGFW_window* win) { return _RGFW->windowState.winLeave == win && _RGFW->windowState.mouseLeave; } +RGFW_bool RGFW_window_didMouseEnter(RGFW_window* win) { return _RGFW->windowState.win == win && _RGFW->windowState.mouseEnter; } +RGFW_bool RGFW_window_isMouseInside(RGFW_window* win) { return win->internal.mouseInside; } + +RGFW_bool RGFW_window_isDataDragging(RGFW_window* win) { return RGFW_window_getDataDrag(win, (i32*)NULL, (i32*)NULL); } +RGFW_bool RGFW_window_didDataDrop(RGFW_window* win) { return RGFW_window_getDataDrop(win, (const char***)NULL, (size_t*)NULL);} + + +RGFW_bool RGFW_window_getDataDrag(RGFW_window* win, i32* x, i32* y) { + if (_RGFW->windowState.win != win || _RGFW->windowState.dataDragging == RGFW_FALSE) return RGFW_FALSE; + if (x) *x = _RGFW->windowState.dropX; + if (y) *y = _RGFW->windowState.dropY; + return RGFW_TRUE; +} +RGFW_bool RGFW_window_getDataDrop(RGFW_window* win, const char*** files, size_t* count) { + if (_RGFW->windowState.win != win || _RGFW->windowState.dataDrop == RGFW_FALSE) return RGFW_FALSE; + if (files) *files = (const char**)_RGFW->files; + if (count) *count = _RGFW->windowState.filesCount; + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_checkEvent(RGFW_window* win, RGFW_event* event) { + if (_RGFW->eventLen == 0 && _RGFW->polledEvents == RGFW_FALSE) { + _RGFW->queueEvents = RGFW_TRUE; + RGFW_pollEvents(); + _RGFW->polledEvents = RGFW_TRUE; + } + + if (RGFW_window_checkQueuedEvent(win, event) == RGFW_FALSE) { + _RGFW->polledEvents = RGFW_FALSE; + return RGFW_FALSE; + } + + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_checkQueuedEvent(RGFW_window* win, RGFW_event* event) { + RGFW_event* ev; + RGFW_ASSERT(win != NULL); + _RGFW->queueEvents = RGFW_TRUE; + /* check queued events */ + ev = RGFW_window_eventQueuePop(win); + if (ev == NULL) return RGFW_FALSE; + + *event = *ev; + return RGFW_TRUE; +} + +RGFW_event* RGFW_window_eventQueuePop(RGFW_window* win) { + RGFW_event* ev = RGFW_eventQueuePop(); + if (ev == NULL) return ev; + + for (i32 i = 1; i < _RGFW->eventLen && ev->common.win != win && ev->common.win != NULL; i++) { + RGFW_eventQueuePush(ev); + ev = RGFW_eventQueuePop(); + } + + if (ev->common.win != win && ev->common.win != NULL) { + return NULL; + } + + return ev; +} + +void RGFW_setRootWindow(RGFW_window* win) { _RGFW->root = win; } +RGFW_window* RGFW_getRootWindow(void) { return _RGFW->root; } + +#ifndef RGFW_EGL +RGFW_bool RGFW_loadEGL(void) { return RGFW_FALSE; } +#endif + +void RGFW_window_setFlagsInternal(RGFW_window* win, RGFW_windowFlags flags, RGFW_windowFlags cmpFlags) { + if (flags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 0); + else if (cmpFlags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 1); + if (flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); + if (flags & RGFW_windowMaximize) RGFW_window_maximize(win); + else if (cmpFlags & RGFW_windowMaximize) RGFW_window_restore(win); + if (flags & RGFW_windowMinimize) RGFW_window_minimize(win); + else if (cmpFlags & RGFW_windowMinimize) RGFW_window_restore(win); + if (flags & RGFW_windowCenter) RGFW_window_center(win); + if (flags & RGFW_windowCenterCursor) RGFW_window_moveMouse(win, win->x + (win->w / 2), win->y + (win->h / 2)); + if (flags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, RGFW_TRUE); + else if (cmpFlags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, 0); + if (flags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 0); + else if (cmpFlags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 1); + if (flags & RGFW_windowHide) RGFW_window_hide(win); + else if (cmpFlags & RGFW_windowHide) RGFW_window_show(win); + if (flags & RGFW_windowFloating) RGFW_window_setFloating(win, 1); + else if (cmpFlags & RGFW_windowFloating) RGFW_window_setFloating(win, 0); + if (flags & RGFW_windowRawMouse) RGFW_window_setRawMouseMode(win, RGFW_TRUE); + else if (cmpFlags & RGFW_windowRawMouse) RGFW_window_setRawMouseMode(win, RGFW_FALSE); + if (flags & RGFW_windowCaptureMouse) RGFW_window_captureRawMouse(win, RGFW_TRUE); + else if (cmpFlags & RGFW_windowCaptureMouse) RGFW_window_captureMouse(win, RGFW_FALSE); + if (flags & RGFW_windowFocus) RGFW_window_focus(win); + + if (flags & RGFW_windowNoResize) { + RGFW_window_setMaxSize(win, win->w, win->h); + RGFW_window_setMinSize(win, win->w, win->h); + } else if (cmpFlags & RGFW_windowNoResize) { + RGFW_window_setMaxSize(win, 0, 0); + RGFW_window_setMinSize(win, 0, 0); + } + + win->internal.flags = flags; +} + + +void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { RGFW_window_setFlagsInternal(win, flags, win->internal.flags); } + +RGFW_bool RGFW_window_isInFocus(RGFW_window* win) { +#ifdef RGFW_WASM + return RGFW_TRUE; +#else + return RGFW_BOOL(win->internal.inFocus); +#endif +} + +void RGFW_setClassName(const char* name) { RGFW_init(); _RGFW->className = name; } + +#ifndef RGFW_X11 +void RGFW_setXInstName(const char* name) { RGFW_UNUSED(name); } +#endif + +RGFW_bool RGFW_window_getMouse(RGFW_window* win, i32* x, i32* y) { + RGFW_ASSERT(win != NULL); + if (x) *x = win->internal.lastMouseX; + if (y) *y = win->internal.lastMouseY; + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_isKeyPressed(RGFW_window* win, RGFW_key key) { return RGFW_isKeyPressed(key) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isKeyDown(RGFW_window* win, RGFW_key key) { return RGFW_isKeyDown(key) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isKeyReleased(RGFW_window* win, RGFW_key key) { return RGFW_isKeyReleased(key) && RGFW_window_isInFocus(win); } + +RGFW_bool RGFW_window_isMousePressed(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMousePressed(button) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isMouseDown(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMouseDown(button) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isMouseReleased(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMouseReleased(button) && RGFW_window_isInFocus(win); } + + + +#ifndef RGFW_X11 +void* RGFW_getDisplay_X11(void) { return NULL; } +u64 RGFW_window_getWindow_X11(RGFW_window* win) { RGFW_UNUSED(win); return 0; } +#endif + +#ifndef RGFW_WAYLAND +struct wl_display* RGFW_getDisplay_Wayland(void) { return NULL; } +struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +#endif + +#ifndef RGFW_WINDOWS +void* RGFW_window_getHWND(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +void* RGFW_window_getHDC(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +#endif + +#ifndef RGFW_MACOS +void* RGFW_window_getView_OSX(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer) { RGFW_UNUSED(win); RGFW_UNUSED(layer); } +void* RGFW_getLayer_OSX(void) { return NULL; } +void* RGFW_window_getWindow_OSX(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +#endif + +void RGFW_setBit(u32* var, u32 mask, RGFW_bool set) { + if (set) *var |= mask; + else *var &= ~mask; +} + +void RGFW_window_center(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_monitor* mon = RGFW_window_getMonitor(win); + if (mon == NULL) return; + + RGFW_window_move(win, (i32)(mon->mode.w - win->w) / 2, (mon->mode.h - win->h) / 2); +} + +RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor* mon, RGFW_window* win) { + RGFW_monitorMode mode; + RGFW_ASSERT(win != NULL); + + mode.w = win->w; + mode.h = win->h; + RGFW_bool ret = RGFW_monitor_requestMode(mon, &mode, RGFW_monitorScale); + + + /* move window to monitor origin so it doesn't move to the next monitor */ + RGFW_window_move(win, mon->x, mon->y); + + return ret; +} + +void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode) { + if (bpp == 32) bpp = 24; + mode->red = mode->green = mode->blue = (u8)(bpp / 3); + + u32 delta = bpp - (mode->red * 3); /* handle leftovers */ + if (delta >= 1) mode->green = mode->green + 1; + if (delta == 2) mode->red = mode->red + 1; +} + +RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode* mon, RGFW_monitorMode* mon2, RGFW_modeRequest request) { + RGFW_ASSERT(mon); + RGFW_ASSERT(mon2); + + return (((mon->w == mon2->w && mon->h == mon2->h) || !(request & RGFW_monitorScale)) && + ((mon->refreshRate == mon2->refreshRate) || !(request & RGFW_monitorRefresh)) && + ((mon->red == mon2->red && mon->green == mon2->green && mon->blue == mon2->blue) || !(request & RGFW_monitorRGB))); +} + +RGFW_bool RGFW_window_shouldClose(RGFW_window* win) { + return (win == NULL || win->internal.shouldClose || (win->internal.exitKey && RGFW_window_isKeyPressed(win, win->internal.exitKey))); +} + +void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose) { + if (shouldClose) { + RGFW_windowQuitCallback(win); + } else { + win->internal.shouldClose = RGFW_FALSE; + } +} + +void RGFW_window_scaleToMonitor(RGFW_window* win) { + RGFW_monitor* monitor = RGFW_window_getMonitor(win); + if (monitor->scaleX == 0 && monitor->scaleY == 0) + return; + + RGFW_window_resize(win, (i32)(monitor->scaleX * (float)win->w), (i32)(monitor->scaleY * (float)win->h)); +} + +void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor* m) { + RGFW_window_move(win, m->x + win->x, m->y + win->y); +} + +RGFW_surface* RGFW_createSurface(u8* data, i32 w, i32 h, RGFW_format format) { + RGFW_surface* surface = (RGFW_surface*)RGFW_ALLOC(sizeof(RGFW_surface)); + RGFW_MEMSET(surface, 0, sizeof(RGFW_surface)); + RGFW_createSurfacePtr(data, w, h, format, surface); + return surface; +} + +void RGFW_surface_free(RGFW_surface* surface) { + RGFW_surface_freePtr(surface); + RGFW_FREE(surface); +} + +RGFW_nativeImage* RGFW_surface_getNativeImage(RGFW_surface* surface) { + return &surface->native; +} + +RGFW_surface* RGFW_window_createSurface(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format) { + RGFW_surface* surface = (RGFW_surface*)RGFW_ALLOC(sizeof(RGFW_surface)); + RGFW_MEMSET(surface, 0, sizeof(RGFW_surface)); + RGFW_window_createSurfacePtr(win, data, w, h, format, surface); + return surface; +} +#ifndef RGFW_X11 +RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_UNUSED(win); + return RGFW_createSurfacePtr(data, w, h, format, surface); +} +#endif + +const RGFW_colorLayout RGFW_layouts[RGFW_formatCount] = { + { 0, 1, 2, 3 }, /* RGFW_formatRGB8 */ + { 2, 1, 0, 3 }, /* RGFW_formatBGR8 */ + { 0, 1, 2, 3 }, /* RGFW_formatRGBA8 */ + { 1, 2, 3, 0 }, /* RGFW_formatARGB8 */ + { 2, 1, 0, 3 }, /* RGFW_formatBGRA8 */ + { 3, 2, 1, 0 }, /* RGFW_formatABGR8 */ +}; + + +void RGFW_copyImageData(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, u8* src_data, RGFW_format src_format) { + RGFW_copyImageData64(dest_data, w, h, dest_format, src_data, src_format, RGFW_FALSE); +} + +void RGFW_copyImageData64(u8* dest_data, i32 dest_w, i32 dest_h, RGFW_format dest_format, u8* src_data, RGFW_format src_format, RGFW_bool is64bit) { + RGFW_ASSERT(dest_data && src_data); + + u32 src_channels = (src_format >= RGFW_formatRGBA8) ? 4 : 3; + u32 dest_channels = (dest_format >= RGFW_formatRGBA8) ? 4 : 3; + + u32 pixel_count = (u32)(dest_w * dest_h); + + if (src_format == dest_format) { + RGFW_MEMCPY(dest_data, src_data, pixel_count * dest_channels); + return; + } + + const RGFW_colorLayout* src_layout = &RGFW_layouts[src_format]; + const RGFW_colorLayout* dest_layout = &RGFW_layouts[dest_format]; + + u32 i, i2 = 0; + for (i = 0; i < pixel_count; i++) { + const u8* src_px = &src_data[i * src_channels]; + u8* dst_px = &dest_data[i2 * dest_channels]; + u8 rgba[4] = {0}; + rgba[0] = src_px[src_layout->r]; + rgba[1] = src_px[src_layout->g]; + rgba[2] = src_px[src_layout->b]; + rgba[3] = 255; + if (src_channels == 4) + rgba[3] = src_px[src_layout->a]; + + dst_px[dest_layout->r] = rgba[0]; + dst_px[dest_layout->g] = rgba[1]; + dst_px[dest_layout->b] = rgba[2]; + if (dest_channels == 4) + dst_px[dest_layout->a] = rgba[3]; + + i2 += 1 + is64bit; + } +} + +RGFW_monitorNode* RGFW_monitors_add(const RGFW_monitor* mon) { + RGFW_monitorNode* node = NULL; + if (_RGFW->monitors.freeList.head == NULL) return node; + + node = _RGFW->monitors.freeList.head; + + _RGFW->monitors.freeList.head = node->next; + if (_RGFW->monitors.freeList.head == NULL) { + _RGFW->monitors.freeList.cur = NULL; + } + + node->next = NULL; + + if (_RGFW->monitors.list.head == NULL) { + _RGFW->monitors.list.head = node; + } else { + _RGFW->monitors.list.cur->next = node; + } + + _RGFW->monitors.list.cur = node; + + if (mon) node->mon = *mon; + node->mon.node = node; + node->disconnected = RGFW_FALSE; + + _RGFW->monitors.count += 1; + return node; +} + +void RGFW_monitors_remove(RGFW_monitorNode* node, RGFW_monitorNode* prev) { + _RGFW->monitors.count -= 1; + + /* remove node from the list */ + if (prev != node) { + prev->next = node->next; + } else { /* node is the head */ + _RGFW->monitors.list.head = NULL; + } + + node->next = NULL; + + /* move node to the free list */ + if (_RGFW->monitors.freeList.head == NULL) { + _RGFW->monitors.freeList.head = node; + } else { + _RGFW->monitors.freeList.cur->next = node; + } + + _RGFW->monitors.freeList.cur = node; +} + +void RGFW_monitors_refresh(void) { + RGFW_monitorNode* prev = _RGFW->monitors.list.head; + for (RGFW_monitorNode* node = _RGFW->monitors.list.head; node; node = node->next) { + if (node->disconnected == RGFW_FALSE) continue; + + RGFW_monitorCallback(_RGFW->root, &node->mon, RGFW_FALSE); + RGFW_monitors_remove(node, prev); + prev = node; + } +} + +RGFW_monitorMode* RGFW_monitor_getModes(RGFW_monitor* monitor, size_t* count) { + size_t num = RGFW_monitor_getModesPtr(monitor, NULL); + RGFW_monitorMode* modes = (RGFW_monitorMode*)RGFW_ALLOC(num * sizeof(RGFW_monitorNode)); + num = RGFW_monitor_getModesPtr(monitor, &modes); + + if (count) *count = num; + return modes; +} + +void RGFW_freeModes(RGFW_monitorMode* modes) { + RGFW_FREE(modes); +} + +RGFW_bool RGFW_monitor_getPosition(RGFW_monitor* monitor, i32* x, i32* y) { + if (x) *x = monitor->x; + if (y) *y = monitor->y; + return RGFW_TRUE; +} + +const char* RGFW_monitor_getName(RGFW_monitor* monitor) { + return monitor->name; +} + +RGFW_bool RGFW_monitor_getScale(RGFW_monitor* monitor, float* x, float* y) { + if (x) *x = monitor->scaleX; + if (y) *y = monitor->scaleY; + return RGFW_TRUE; +} + +RGFW_bool RGFW_monitor_getPhysicalSize(RGFW_monitor* monitor, float* w, float* h) { + if (w) *w = monitor->physW; + if (h) *h = monitor->physH; + return RGFW_TRUE; +} + +void RGFW_monitor_setUserPtr(RGFW_monitor* monitor, void* userPtr) { + monitor->userPtr = userPtr; +} + +void* RGFW_monitor_getUserPtr(RGFW_monitor* monitor) { + return monitor->userPtr; +} + +RGFW_bool RGFW_monitor_getMode(RGFW_monitor* monitor, RGFW_monitorMode* mode) { + if (mode) *mode = monitor->mode; + return RGFW_TRUE; +} + +RGFW_gammaRamp* RGFW_monitor_getGammaRamp(RGFW_monitor* monitor) { + RGFW_gammaRamp* ramp = (RGFW_gammaRamp*)RGFW_ALLOC(sizeof(RGFW_gammaRamp)); + ramp->count = RGFW_monitor_getGammaRampPtr(monitor, NULL); + ramp->red = (u16*)RGFW_ALLOC(sizeof(u16) * ramp->count); + ramp->green = (u16*)RGFW_ALLOC(sizeof(u16) * ramp->count); + ramp->blue = (u16*)RGFW_ALLOC(sizeof(u16) * ramp->count); + ramp->count = RGFW_monitor_getGammaRampPtr(monitor, ramp); + + return ramp; +} + +void RGFW_freeGammaRamp(RGFW_gammaRamp* ramp) { + RGFW_FREE(ramp->red); + RGFW_FREE(ramp->green); + RGFW_FREE(ramp->blue); + RGFW_FREE(ramp); +} + +RGFW_bool RGFW_monitor_setGammaPtr(RGFW_monitor* monitor, float gamma, u16* ptr, size_t count) { + RGFW_ASSERT(monitor); + RGFW_ASSERT(gamma > 0.0f); + + size_t i; + for (i = 0; i < count; i++) { + float value = (float)i / (float) (count - 1); + #ifndef RGFW_NO_MATH + value = powf(value, 1.f / gamma) * 65535.f + 0.5f; + #endif + value = RGFW_MIN(value, 65535.f); + + ptr[i] = (u16)value; + } + + RGFW_gammaRamp ramp; + ramp.red = ptr; + ramp.green = ptr; + ramp.blue = ptr; + ramp.count = count; + + return RGFW_monitor_setGammaRamp(monitor, &ramp); +} + +RGFW_bool RGFW_monitor_setGamma(RGFW_monitor* monitor, float gamma) { + size_t count = RGFW_monitor_getGammaRampPtr(monitor, NULL); + u16* ptr = (u16*)RGFW_ALLOC(count * sizeof(u16)); + + RGFW_bool ret = RGFW_monitor_setGammaPtr(monitor, gamma, ptr, count); + RGFW_FREE(ptr); + + return ret; +} + +RGFW_monitor** RGFW_getMonitors(size_t* len) { + static RGFW_monitor* monitors[RGFW_MAX_MONITORS]; + RGFW_init(); + if (len != NULL) { + *len = _RGFW->monitors.count; + } + + u8 i = 0; + RGFW_monitorNode* cur_node = _RGFW->monitors.list.head; + while (cur_node != NULL) { + monitors[i] = &cur_node->mon; + i++; + cur_node = cur_node->next; + } + return monitors; +} + +RGFW_monitor* RGFW_getPrimaryMonitor(void) { + if (_RGFW->monitors.primary == NULL) { + _RGFW->monitors.primary = _RGFW->monitors.list.head; + } + + return &_RGFW->monitors.primary->mon; +} + +RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format) { + return RGFW_window_setIconEx(win, data, w, h, format, RGFW_iconBoth); +} + +void RGFW_window_captureMouse(RGFW_window* win, RGFW_bool state) { + win->internal.captureMouse = state; + RGFW_window_captureMousePlatform(win, state); +} + +void RGFW_window_setRawMouseMode(RGFW_window* win, RGFW_bool state) { + win->internal.rawMouse = state; + RGFW_window_setRawMouseModePlatform(win, state); +} + +void RGFW_window_captureRawMouse(RGFW_window* win, RGFW_bool state) { + RGFW_window_captureMouse(win, state); + RGFW_window_setRawMouseMode(win, state); +} + +RGFW_bool RGFW_window_isRawMouseMode(RGFW_window* win) { return RGFW_BOOL(win->internal.rawMouse); } +RGFW_bool RGFW_window_isCaptured(RGFW_window* win) { return RGFW_BOOL(win->internal.captureMouse); } + +void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value) { + if (value) win->internal.mod |= mod; + else win->internal.mod &= ~mod; +} + +void RGFW_updateKeyModsEx(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { + RGFW_updateKeyMod(win, RGFW_modCapsLock, capital); + RGFW_updateKeyMod(win, RGFW_modNumLock, numlock); + RGFW_updateKeyMod(win, RGFW_modControl, control); + RGFW_updateKeyMod(win, RGFW_modAlt, alt); + RGFW_updateKeyMod(win, RGFW_modShift, shift); + RGFW_updateKeyMod(win, RGFW_modSuper, super); + RGFW_updateKeyMod(win, RGFW_modScrollLock, scroll); +} + +void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll) { + RGFW_updateKeyModsEx(win, capital, numlock, + RGFW_window_isKeyDown(win, RGFW_controlL) || RGFW_window_isKeyDown(win, RGFW_controlR), + RGFW_window_isKeyDown(win, RGFW_altL) || RGFW_window_isKeyDown(win, RGFW_altR), + RGFW_window_isKeyDown(win, RGFW_shiftL) || RGFW_window_isKeyDown(win, RGFW_shiftR), + RGFW_window_isKeyDown(win, RGFW_superL) || RGFW_window_isKeyDown(win, RGFW_superR), + scroll); +} + +void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show) { + if (show && (win->internal.flags & RGFW_windowHideMouse)) + win->internal.flags ^= RGFW_windowHideMouse; + else if (!show && !(win->internal.flags & RGFW_windowHideMouse)) + win->internal.flags |= RGFW_windowHideMouse; +} + +RGFW_bool RGFW_window_isMouseHidden(RGFW_window* win) { + return (RGFW_bool)RGFW_BOOL(((RGFW_window*)win)->internal.flags & RGFW_windowHideMouse); +} + +RGFW_bool RGFW_window_borderless(RGFW_window* win) { + return (RGFW_bool)RGFW_BOOL(win->internal.flags & RGFW_windowNoBorder); +} + +RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->internal.flags & RGFW_windowFullscreen); } +RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->internal.flags & RGFW_windowAllowDND); } + +#ifndef RGFW_WINDOWS +void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { + RGFW_setBit(&win->internal.flags, RGFW_windowAllowDND, allow); +} +#endif + +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WASM) || defined(RGFW_WAYLAND) +#ifndef __USE_POSIX199309 + #define __USE_POSIX199309 +#endif +#include +struct timespec; +#endif + +#if defined(RGFW_WAYLAND) || defined(RGFW_X11) || defined(RGFW_WINDOWS) +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, show); + if (show == RGFW_FALSE) + RGFW_window_setMouse(win, _RGFW->hiddenMouse); + else + RGFW_window_setMouseDefault(win); +} +#endif + +#ifndef RGFW_MACOS +void RGFW_moveToMacOSResourceDir(void) { } +#endif + +/* + graphics API specific code (end of generic code) + starts here +*/ + + +/* + OpenGL defines start here (Normal, EGL, OSMesa) +*/ + +#if defined(RGFW_OPENGL) +/* EGL, OpenGL */ +#define RGFW_DEFAULT_GL_HINTS { \ + /* Stencil */ 0, \ + /* Samples */ 0, \ + /* Stereo */ RGFW_FALSE, \ + /* AuxBuffers */ 0, \ + /* DoubleBuffer */ RGFW_TRUE, \ + /* Red */ 8, \ + /* Green */ 8, \ + /* Blue */ 8, \ + /* Alpha */ 8, \ + /* Depth */ 24, \ + /* AccumRed */ 0, \ + /* AccumGreen */ 0, \ + /* AccumBlue */ 0, \ + /* AccumAlpha */ 0, \ + /* SRGB */ RGFW_FALSE, \ + /* Robustness */ RGFW_FALSE, \ + /* Debug */ RGFW_FALSE, \ + /* NoError */ RGFW_FALSE, \ + /* ReleaseBehavior */ RGFW_glReleaseNone, \ + /* Profile */ RGFW_glCore, \ + /* Major */ 1, \ + /* Minor */ 0, \ + /* Share */ NULL, \ + /* Share_EGL */ NULL, \ + /* renderer */ RGFW_glAccelerated \ +} + +RGFW_glHints RGFW_globalHints_OpenGL_SRC = RGFW_DEFAULT_GL_HINTS; +RGFW_glHints* RGFW_globalHints_OpenGL = &RGFW_globalHints_OpenGL_SRC; + +void RGFW_resetGlobalHints_OpenGL(void) { +#if !defined(__cplusplus) || defined(RGFW_MACOS) + RGFW_globalHints_OpenGL_SRC = (RGFW_glHints)RGFW_DEFAULT_GL_HINTS; +#else + RGFW_globalHints_OpenGL_SRC = RGFW_DEFAULT_GL_HINTS; +#endif +} +void RGFW_setGlobalHints_OpenGL(RGFW_glHints* hints) { RGFW_globalHints_OpenGL = hints; } +RGFW_glHints* RGFW_getGlobalHints_OpenGL(void) { RGFW_init(); return RGFW_globalHints_OpenGL; } + + +void* RGFW_glContext_getSourceContext(RGFW_glContext* ctx) { + RGFW_UNUSED(ctx); + +#ifdef RGFW_WAYLAND + if (RGFW_usingWayland()) return (void*)ctx->egl.ctx; +#endif + +#if defined(RGFW_X11) + return (void*)ctx->ctx; +#else + return NULL; +#endif +} + +RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints) { + #ifdef RGFW_WAYLAND + if (RGFW_usingWayland()) { + return (RGFW_glContext*)RGFW_window_createContext_EGL(win, hints); + } + #endif + RGFW_glContext* ctx = (RGFW_glContext*)RGFW_ALLOC(sizeof(RGFW_glContext)); + if (RGFW_window_createContextPtr_OpenGL(win, ctx, hints) == RGFW_FALSE) { + RGFW_FREE(ctx); + win->src.ctx.native = NULL; + return NULL; + } + win->src.gfxType |= RGFW_gfxOwnedByRGFW; + return ctx; +} + +RGFW_glContext* RGFW_window_getContext_OpenGL(RGFW_window* win) { + if (win->src.gfxType & RGFW_windowEGL) return NULL; + return win->src.ctx.native; +} + +void RGFW_window_deleteContext_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + RGFW_window_deleteContextPtr_OpenGL(win, ctx); + if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(ctx); +} + +RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, size_t len) { + const char *start = extensions; + const char *where; + const char* terminator; + + if (extensions == NULL || ext == NULL) { + return RGFW_FALSE; + } + + while (ext[len - 1] == '\0' && len > 3) { + len--; + } + + where = RGFW_STRSTR(extensions, ext); + while (where) { + terminator = where + len; + if ((where == start || *(where - 1) == ' ') && + (*terminator == ' ' || *terminator == '\0')) { + return RGFW_TRUE; + } + where = RGFW_STRSTR(terminator, ext); + } + + return RGFW_FALSE; +} + +RGFWDEF RGFW_bool RGFW_extensionSupported_base(const char* extension, size_t len); +RGFW_bool RGFW_extensionSupported_base(const char* extension, size_t len) { + #ifdef GL_NUM_EXTENSIONS + if (RGFW_globalHints_OpenGL->major >= 3) { + i32 i; + + GLint count = 0; + + RGFW_proc RGFW_glGetStringi = RGFW_getProcAddress_OpenGL("glGetStringi"); + RGFW_proc RGFW_glGetIntegerv = RGFW_getProcAddress_OpenGL("glGetIntegerv"); + if (RGFW_glGetIntegerv) + ((void(*)(GLenum, GLint*))RGFW_glGetIntegerv)(GL_NUM_EXTENSIONS, &count); + + for (i = 0; RGFW_glGetStringi && i < count; i++) { + const char* en = ((const char* (*)(u32, u32))RGFW_glGetStringi)(GL_EXTENSIONS, (u32)i); + if (en && RGFW_STRNCMP(en, extension, len) == 0) { + return RGFW_TRUE; + } + } + } else +#endif + { + RGFW_proc RGFW_glGetString = RGFW_getProcAddress_OpenGL("glGetString"); + #define RGFW_GL_EXTENSIONS 0x1F03 + if (RGFW_glGetString) { + const char* extensions = ((const char*(*)(u32))RGFW_glGetString)(RGFW_GL_EXTENSIONS); + + if ((extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len)) { + return RGFW_TRUE; + } + } + } + return RGFW_FALSE; +} + +RGFW_bool RGFW_extensionSupported_OpenGL(const char* extension, size_t len) { + if (RGFW_extensionSupported_base(extension, len)) return RGFW_TRUE; + return RGFW_extensionSupportedPlatform_OpenGL(extension, len); +} + +void RGFW_window_makeCurrentWindow_OpenGL(RGFW_window* win) { + if (win) { + _RGFW->current = win; + } + + RGFW_window_makeCurrentContext_OpenGL(win); +} + +RGFW_window* RGFW_getCurrentWindow_OpenGL(void) { return _RGFW->current; } +void RGFW_attribStack_init(RGFW_attribStack* stack, i32* attribs, size_t max) { stack->attribs = attribs; stack->count = 0; stack->max = max; } +void RGFW_attribStack_pushAttrib(RGFW_attribStack* stack, i32 attrib) { + RGFW_ASSERT(stack->count < stack->max); + stack->attribs[stack->count] = attrib; + stack->count += 1; +} +void RGFW_attribStack_pushAttribs(RGFW_attribStack* stack, i32 attrib1, i32 attrib2) { + RGFW_attribStack_pushAttrib(stack, attrib1); + RGFW_attribStack_pushAttrib(stack, attrib2); +} + +/* EGL */ +#ifdef RGFW_EGL +#include + +PFNEGLINITIALIZEPROC RGFW_eglInitialize; +PFNEGLGETCONFIGSPROC RGFW_eglGetConfigs; +PFNEGLCHOOSECONFIGPROC RGFW_eglChooseConfig; +PFNEGLCREATEWINDOWSURFACEPROC RGFW_eglCreateWindowSurface; +PFNEGLCREATECONTEXTPROC RGFW_eglCreateContext; +PFNEGLMAKECURRENTPROC RGFW_eglMakeCurrent; +PFNEGLGETDISPLAYPROC RGFW_eglGetDisplay; +PFNEGLSWAPBUFFERSPROC RGFW_eglSwapBuffers; +PFNEGLSWAPINTERVALPROC RGFW_eglSwapInterval; +PFNEGLBINDAPIPROC RGFW_eglBindAPI; +PFNEGLDESTROYCONTEXTPROC RGFW_eglDestroyContext; +PFNEGLTERMINATEPROC RGFW_eglTerminate; +PFNEGLDESTROYSURFACEPROC RGFW_eglDestroySurface; +PFNEGLGETCURRENTCONTEXTPROC RGFW_eglGetCurrentContext; +PFNEGLGETPROCADDRESSPROC RGFW_eglGetProcAddress = NULL; +PFNEGLQUERYSTRINGPROC RGFW_eglQueryString; +PFNEGLGETCONFIGATTRIBPROC RGFW_eglGetConfigAttrib; + +#define EGL_SURFACE_MAJOR_VERSION_KHR 0x3098 +#define EGL_SURFACE_MINOR_VERSION_KHR 0x30fb + +#ifdef RGFW_WINDOWS + #include +#elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + #include +#endif + +#ifdef RGFW_WAYLAND +#include +#endif + +void* RGFW_eglLibHandle = NULL; + +void* RGFW_getDisplay_EGL(void) { return _RGFW->EGL_display; } +void* RGFW_eglContext_getSourceContext(RGFW_eglContext* ctx) { return ctx->ctx; } +void* RGFW_eglContext_getSurface(RGFW_eglContext* ctx) { return ctx->surface; } +struct wl_egl_window* RGFW_eglContext_wlEGLWindow(RGFW_eglContext* ctx) { return ctx->eglWindow; } + +RGFW_bool RGFW_loadEGL(void) { + RGFW_init(); + if (RGFW_eglGetProcAddress != NULL) { + return RGFW_TRUE; + } + +#ifndef RGFW_WASM + #ifdef RGFW_WINDOWS + const char* libNames[] = { "libEGL.dll", "EGL.dll" }; + #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + /* Linux and macOS */ + const char* libNames[] = { + "libEGL.so.1", /* most common */ + "libEGL.so", /* fallback */ + "/System/Library/Frameworks/OpenGL.framework/OpenGL" /* fallback for older macOS EGL-like systems */ + }; + #endif + + for (size_t i = 0; i < sizeof(libNames) / sizeof(libNames[0]); i++) { + #ifdef RGFW_WINDOWS + RGFW_eglLibHandle = (void*)LoadLibraryA(libNames[i]); + if (RGFW_eglLibHandle) { + RGFW_eglGetProcAddress = (PFNEGLGETPROCADDRESSPROC)(RGFW_proc)GetProcAddress((HMODULE)RGFW_eglLibHandle, "eglGetProcAddress"); + break; + } + #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + RGFW_eglLibHandle = dlopen(libNames[i], RTLD_LAZY | RTLD_GLOBAL); + if (RGFW_eglLibHandle) { + void* lib = dlsym(RGFW_eglLibHandle, "eglGetProcAddress"); + if (lib != NULL) RGFW_MEMCPY(&RGFW_eglGetProcAddress, &lib, sizeof(PFNEGLGETPROCADDRESSPROC)); + break; + } + #endif + } + + if (!RGFW_eglLibHandle || !RGFW_eglGetProcAddress) { + return RGFW_FALSE; + } + + RGFW_eglInitialize = (PFNEGLINITIALIZEPROC) RGFW_eglGetProcAddress("eglInitialize"); + RGFW_eglGetConfigs = (PFNEGLGETCONFIGSPROC) RGFW_eglGetProcAddress("eglGetConfigs"); + RGFW_eglChooseConfig = (PFNEGLCHOOSECONFIGPROC) RGFW_eglGetProcAddress("eglChooseConfig"); + RGFW_eglCreateWindowSurface = (PFNEGLCREATEWINDOWSURFACEPROC) RGFW_eglGetProcAddress("eglCreateWindowSurface"); + RGFW_eglCreateContext = (PFNEGLCREATECONTEXTPROC) RGFW_eglGetProcAddress("eglCreateContext"); + RGFW_eglMakeCurrent = (PFNEGLMAKECURRENTPROC) RGFW_eglGetProcAddress("eglMakeCurrent"); + RGFW_eglGetDisplay = (PFNEGLGETDISPLAYPROC) RGFW_eglGetProcAddress("eglGetDisplay"); + RGFW_eglSwapBuffers = (PFNEGLSWAPBUFFERSPROC) RGFW_eglGetProcAddress("eglSwapBuffers"); + RGFW_eglSwapInterval = (PFNEGLSWAPINTERVALPROC) RGFW_eglGetProcAddress("eglSwapInterval"); + RGFW_eglBindAPI = (PFNEGLBINDAPIPROC) RGFW_eglGetProcAddress("eglBindAPI"); + RGFW_eglDestroyContext = (PFNEGLDESTROYCONTEXTPROC) RGFW_eglGetProcAddress("eglDestroyContext"); + RGFW_eglTerminate = (PFNEGLTERMINATEPROC) RGFW_eglGetProcAddress("eglTerminate"); + RGFW_eglDestroySurface = (PFNEGLDESTROYSURFACEPROC) RGFW_eglGetProcAddress("eglDestroySurface"); + RGFW_eglQueryString = (PFNEGLQUERYSTRINGPROC) RGFW_eglGetProcAddress("eglQueryString"); + RGFW_eglGetCurrentContext = (PFNEGLGETCURRENTCONTEXTPROC) RGFW_eglGetProcAddress("eglGetCurrentContext"); + RGFW_eglGetConfigAttrib = (PFNEGLGETCONFIGATTRIBPROC) RGFW_eglGetProcAddress("eglGetConfigAttrib"); + +#else + RGFW_eglGetProcAddress = eglGetProcAddress; + RGFW_eglInitialize = (PFNEGLINITIALIZEPROC) eglInitialize; + RGFW_eglGetConfigs = (PFNEGLGETCONFIGSPROC) eglGetConfigs; + RGFW_eglChooseConfig = (PFNEGLCHOOSECONFIGPROC) eglChooseConfig; + RGFW_eglCreateWindowSurface = (PFNEGLCREATEWINDOWSURFACEPROC) eglCreateWindowSurface; + RGFW_eglCreateContext = (PFNEGLCREATECONTEXTPROC) eglCreateContext; + RGFW_eglMakeCurrent = (PFNEGLMAKECURRENTPROC) eglMakeCurrent; + RGFW_eglGetDisplay = (PFNEGLGETDISPLAYPROC) eglGetDisplay; + RGFW_eglSwapBuffers = (PFNEGLSWAPBUFFERSPROC) eglSwapBuffers; + RGFW_eglSwapInterval = (PFNEGLSWAPINTERVALPROC) eglSwapInterval; + RGFW_eglBindAPI = (PFNEGLBINDAPIPROC) eglBindAPI; + RGFW_eglDestroyContext = (PFNEGLDESTROYCONTEXTPROC) eglDestroyContext; + RGFW_eglTerminate = (PFNEGLTERMINATEPROC) eglTerminate; + RGFW_eglDestroySurface = (PFNEGLDESTROYSURFACEPROC) eglDestroySurface; + RGFW_eglQueryString = (PFNEGLQUERYSTRINGPROC) eglQueryString; + RGFW_eglGetCurrentContext = (PFNEGLGETCURRENTCONTEXTPROC) eglGetCurrentContext; + RGFW_eglGetConfigAttrib = (PFNEGLGETCONFIGATTRIBPROC)eglGetConfigAttrib; +#endif + + RGFW_bool out = RGFW_BOOL(RGFW_eglInitialize!= NULL && + RGFW_eglGetConfigs!= NULL && + RGFW_eglChooseConfig!= NULL && + RGFW_eglCreateWindowSurface!= NULL && + RGFW_eglCreateContext!= NULL && + RGFW_eglMakeCurrent!= NULL && + RGFW_eglGetDisplay!= NULL && + RGFW_eglSwapBuffers!= NULL && + RGFW_eglSwapInterval != NULL && + RGFW_eglBindAPI!= NULL && + RGFW_eglDestroyContext!= NULL && + RGFW_eglTerminate!= NULL && + RGFW_eglDestroySurface!= NULL && + RGFW_eglQueryString != NULL && + RGFW_eglGetCurrentContext != NULL && + RGFW_eglGetConfigAttrib != NULL); + + if (out) { + #ifdef RGFW_WINDOWS + HDC dc = GetDC(NULL); + _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) dc); + ReleaseDC(NULL, dc); + #elif defined(RGFW_WAYLAND) + if (_RGFW->useWaylandBool) + _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) _RGFW->wl_display); + else + #endif + #ifdef RGFW_X11 + _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) _RGFW->display); + #else + {} + #endif + #if !defined(RGFW_WAYLAND) && !defined(RGFW_WINDOWS) && !defined(RGFW_X11) + _RGFW->EGL_display = RGFW_eglGetDisplay(EGL_DEFAULT_DISPLAY); + #endif + } + + RGFW_eglInitialize(_RGFW->EGL_display, NULL, NULL); + return out; +} + + +void RGFW_unloadEGL(void) { + if (!RGFW_eglLibHandle) return; + RGFW_eglTerminate(_RGFW->EGL_display); + #ifdef RGFW_WINDOWS + FreeLibrary((HMODULE)RGFW_eglLibHandle); + #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + dlclose(RGFW_eglLibHandle); + #endif + + RGFW_eglLibHandle = NULL; + RGFW_eglGetProcAddress = NULL; +} + +RGFW_bool RGFW_window_createContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx, RGFW_glHints* hints) { + if (RGFW_loadEGL() == RGFW_FALSE) return RGFW_FALSE; + win->src.ctx.egl = ctx; + win->src.gfxType = RGFW_gfxEGL; + +#ifdef RGFW_WAYLAND + if (_RGFW->useWaylandBool) + win->src.ctx.egl->eglWindow = wl_egl_window_create(win->src.surface, win->w, win->h); +#endif + + #ifndef EGL_OPENGL_ES1_BIT + #define EGL_OPENGL_ES1_BIT 0x1 + #endif + + EGLint egl_config[24]; + + { + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, egl_config, 24); + + RGFW_attribStack_pushAttribs(&stack, EGL_SURFACE_TYPE, EGL_WINDOW_BIT); + RGFW_attribStack_pushAttrib(&stack, EGL_RENDERABLE_TYPE); + + if (hints->profile == RGFW_glES) { + switch (hints->major) { + case 1: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES1_BIT); break; + case 2: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES2_BIT); break; + case 3: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES3_BIT); break; + default: break; + } + } else { + RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_BIT); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_RED_SIZE, hints->red); + RGFW_attribStack_pushAttribs(&stack, EGL_GREEN_SIZE, hints->green); + RGFW_attribStack_pushAttribs(&stack, EGL_BLUE_SIZE, hints->blue); + RGFW_attribStack_pushAttribs(&stack, EGL_ALPHA_SIZE, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, EGL_DEPTH_SIZE, hints->depth); + + RGFW_attribStack_pushAttribs(&stack, EGL_STENCIL_SIZE, hints->stencil); + if (hints->samples) { + RGFW_attribStack_pushAttribs(&stack, EGL_SAMPLE_BUFFERS, 1); + RGFW_attribStack_pushAttribs(&stack, EGL_SAMPLES, hints->samples); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); + } + + EGLint numConfigs, best_config = -1, best_samples = 0; + + RGFW_eglChooseConfig(_RGFW->EGL_display, egl_config, NULL, 0, &numConfigs); + EGLConfig* configs = (EGLConfig*)RGFW_ALLOC(sizeof(EGLConfig) * (u32)numConfigs); + + RGFW_eglChooseConfig(_RGFW->EGL_display, egl_config, configs, numConfigs, &numConfigs); + +#ifdef RGFW_X11 + RGFW_bool transparent = (win->internal.flags & RGFW_windowTransparent); + EGLint best_depth = 0; +#endif + + for (EGLint i = 0; i < numConfigs; i++) { + EGLint visual_id = 0; + EGLint samples = 0; + + RGFW_eglGetConfigAttrib(_RGFW->EGL_display, configs[i], EGL_NATIVE_VISUAL_ID, &visual_id); + RGFW_eglGetConfigAttrib(_RGFW->EGL_display, configs[i], EGL_SAMPLES, &samples); + + if (best_config == -1) best_config = i; + +#ifdef RGFW_X11 + if (_RGFW->useWaylandBool == RGFW_FALSE) { + XVisualInfo vinfo_template; + vinfo_template.visualid = (VisualID)visual_id; + + int num_visuals = 0; + XVisualInfo* vi = XGetVisualInfo(_RGFW->display, VisualIDMask, &vinfo_template, &num_visuals); + if (!vi) continue; + if ((!transparent || vi->depth == 32) && best_depth == 0) { + best_config = i; + best_depth = vi->depth; + } + + if ((!(transparent) || vi->depth == 32) && (samples <= hints->samples && samples > best_samples)) { + best_depth = vi->depth; + best_config = i; + best_samples = samples; + XFree(vi); + continue; + } + } +#endif + + if (samples <= hints->samples && samples > best_samples) { + best_config = i; + best_samples = samples; + } + } + + EGLConfig config = configs[best_config]; + RGFW_FREE(configs); +#ifdef RGFW_X11 + if (_RGFW->useWaylandBool == RGFW_FALSE) { + /* This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used */ + XVisualInfo* result; + XVisualInfo desired; + EGLint visualID = 0, count = 0; + + RGFW_eglGetConfigAttrib(_RGFW->EGL_display, config, EGL_NATIVE_VISUAL_ID, &visualID); + if (visualID) { + desired.visualid = (VisualID)visualID; + result = XGetVisualInfo(_RGFW->display, VisualIDMask, &desired, &count); + } else RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to fetch a valid EGL VisualID"); + + if (result == NULL || count == 0) { + if (win->src.window == 0) { + /* try to create a EGL context anyway (this will work if you're not using a NVidia driver) */ + win->internal.flags &= ~(u32)RGFW_windowEGL; + RGFW_createWindowPlatform("", win->internal.flags, win); + } + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to find a valid visual for the EGL config"); + } else { + RGFW_bool showWindow = RGFW_FALSE; + if (win->src.window) { + showWindow = (RGFW_window_isMinimized(win) == RGFW_FALSE); + RGFW_window_closePlatform(win); + } + + RGFW_XCreateWindow(*result, "", win->internal.flags, win); + + if (showWindow) { + RGFW_window_show(win); + } + XFree(result); + } + } +#endif + + EGLint surf_attribs[9]; + + { + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, surf_attribs, 9); + + const char present_opaque_str[] = "EGL_EXT_present_opaque"; + RGFW_bool opaque_extension_Found = RGFW_extensionSupportedPlatform_EGL(present_opaque_str, sizeof(present_opaque_str)); + + #ifndef EGL_PRESENT_OPAQUE_EXT + #define EGL_PRESENT_OPAQUE_EXT 0x31df + #endif + + #ifndef EGL_GL_COLORSPACE_KHR + #define EGL_GL_COLORSPACE_KHR 0x309D + #ifndef EGL_GL_COLORSPACE_SRGB_KHR + #define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 + #endif + #endif + + const char gl_colorspace_str[] = "EGL_KHR_gl_colorspace"; + RGFW_bool gl_colorspace_Found = RGFW_extensionSupportedPlatform_EGL(gl_colorspace_str, sizeof(gl_colorspace_str)); + + if (hints->sRGB && gl_colorspace_Found) { + RGFW_attribStack_pushAttribs(&stack, EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR); + } + + if (!(win->internal.flags & RGFW_windowTransparent) && opaque_extension_Found) + RGFW_attribStack_pushAttribs(&stack, EGL_PRESENT_OPAQUE_EXT, EGL_TRUE); + + if (hints->doubleBuffer == 0) { + RGFW_attribStack_pushAttribs(&stack, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); + } + #if defined(RGFW_MACOS) + void* layer = RGFW_getLayer_OSX(); + + RGFW_window_setLayer_OSX(win, layer); + + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) layer, surf_attribs); + #elif defined(RGFW_WINDOWS) + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.window, surf_attribs); + #elif defined(RGFW_WAYLAND) + if (_RGFW->useWaylandBool) + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.ctx.egl->eglWindow, surf_attribs); + else + #endif + #ifdef RGFW_X11 + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.window, surf_attribs); + #else + {} + #endif + #ifdef RGFW_WASM + win->src.ctx.egl->surface = eglCreateWindowSurface(_RGFW->EGL_display, config, 0, 0); + #endif + + if (win->src.ctx.egl->surface == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to create an EGL surface."); + return RGFW_FALSE; + } + + EGLint attribs[20]; + { + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, attribs, 20); + + if (hints->major || hints->minor) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_MAJOR_VERSION, hints->major); + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_MINOR_VERSION, hints->minor); + } + + if (hints->profile == RGFW_glCore) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + } else if (hints->profile == RGFW_glCompatibility) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + } else if (hints->profile == RGFW_glForwardCompatibility) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE, EGL_TRUE); + } + + + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_ROBUST_ACCESS, hints->robustness); + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_DEBUG, hints->debug); + + #ifndef EGL_CONTEXT_RELEASE_BEHAVIOR_KHR + #define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 + #endif + + #ifndef EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR + #define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 + #endif + + if (hints->releaseBehavior == RGFW_glReleaseFlush) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR); + } else { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, 0x0000); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); + } + + if (hints->profile == RGFW_glES) + RGFW_eglBindAPI(EGL_OPENGL_ES_API); + else + RGFW_eglBindAPI(EGL_OPENGL_API); + + win->src.ctx.egl->ctx = RGFW_eglCreateContext(_RGFW->EGL_display, config, hints->shareEGL, attribs); + + if (win->src.ctx.egl->ctx == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to create an EGL context."); + return RGFW_FALSE; + } + + RGFW_eglMakeCurrent(_RGFW->EGL_display, win->src.ctx.egl->surface, win->src.ctx.egl->surface, win->src.ctx.egl->ctx); + RGFW_eglSwapBuffers(_RGFW->EGL_display, win->src.ctx.egl->surface); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL context initalized."); + return RGFW_TRUE; +} + +RGFW_eglContext* RGFW_window_getContext_EGL(RGFW_window* win) { + if (win->src.gfxType == RGFW_windowOpenGL) return NULL; + return win->src.ctx.egl; +} + +void RGFW_window_deleteContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx) { + if (_RGFW->EGL_display == NULL) return; + + RGFW_eglDestroySurface(_RGFW->EGL_display, ctx->surface); + RGFW_eglDestroyContext(_RGFW->EGL_display, ctx->ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL context freed"); + #ifdef RGFW_WAYLAND + if (_RGFW->useWaylandBool == RGFW_FALSE) return; + wl_egl_window_destroy(win->src.ctx.egl->eglWindow); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL window context freed"); + #endif + win->src.ctx.egl = NULL; +} + +void RGFW_window_makeCurrentContext_EGL(RGFW_window* win) { if (win) RGFW_ASSERT(win->src.ctx.egl); + if (win == NULL) + RGFW_eglMakeCurrent(_RGFW->EGL_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + else { + RGFW_eglMakeCurrent(_RGFW->EGL_display, win->src.ctx.egl->surface, win->src.ctx.egl->surface, win->src.ctx.egl->ctx); + } +} + +void RGFW_window_swapBuffers_EGL(RGFW_window* win) { + if (RGFW_eglSwapBuffers) + RGFW_eglSwapBuffers(_RGFW->EGL_display, win->src.ctx.egl->surface); + else RGFW_window_swapBuffers_OpenGL(win); +} + +void* RGFW_getCurrentContext_EGL(void) { + return RGFW_eglGetCurrentContext(); +} + +RGFW_proc RGFW_getProcAddress_EGL(const char* procname) { + #if defined(RGFW_WINDOWS) + RGFW_proc proc = (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); + + if (proc) + return proc; + #endif + + return (RGFW_proc) RGFW_eglGetProcAddress(procname); +} + +RGFW_bool RGFW_extensionSupportedPlatform_EGL(const char* extension, size_t len) { + if (RGFW_loadEGL() == RGFW_FALSE) return RGFW_FALSE; + const char* extensions = RGFW_eglQueryString(_RGFW->EGL_display, EGL_EXTENSIONS); + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +} + +void RGFW_window_swapInterval_EGL(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); + RGFW_eglSwapInterval(_RGFW->EGL_display, swapInterval); +} + +RGFW_bool RGFW_extensionSupported_EGL(const char* extension, size_t len) { + if (RGFW_extensionSupported_base(extension, len)) return RGFW_TRUE; + return RGFW_extensionSupportedPlatform_EGL(extension, len); +} + +void RGFW_window_makeCurrentWindow_EGL(RGFW_window* win) { + _RGFW->current = win; + RGFW_window_makeCurrentContext_EGL(win); +} + +RGFW_window* RGFW_getCurrentWindow_EGL(void) { return _RGFW->current; } + +RGFW_eglContext* RGFW_window_createContext_EGL(RGFW_window* win, RGFW_glHints* hints) { + RGFW_eglContext* ctx = (RGFW_eglContext*)RGFW_ALLOC(sizeof(RGFW_eglContext)); + if (RGFW_window_createContextPtr_EGL(win, ctx, hints) == RGFW_FALSE) { + RGFW_FREE(ctx); + win->src.ctx.egl = NULL; + return NULL; + } + win->src.gfxType |= RGFW_gfxOwnedByRGFW; + return ctx; +} + +void RGFW_window_deleteContext_EGL(RGFW_window* win, RGFW_eglContext* ctx) { + RGFW_window_deleteContextPtr_EGL(win, ctx); + if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(ctx); +} + +#endif /* RGFW_EGL */ + +/* + end of RGFW_EGL defines +*/ +#endif /* end of RGFW_GL (OpenGL, EGL, OSMesa )*/ + +/* + RGFW_VULKAN defines +*/ +#ifdef RGFW_VULKAN +#ifdef RGFW_MACOS +#include +#endif + +const char** RGFW_getRequiredInstanceExtensions_Vulkan(size_t* count) { + static const char* arr[2] = {VK_KHR_SURFACE_EXTENSION_NAME}; + arr[1] = RGFW_VK_SURFACE; + if (count != NULL) *count = 2; + + return (const char**)arr; +} + +#ifndef RGFW_MACOS +VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { + RGFW_ASSERT(win != NULL); RGFW_ASSERT(instance); + RGFW_ASSERT(surface != NULL); + + *surface = VK_NULL_HANDLE; + +#ifdef RGFW_X11 + + VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) _RGFW->display, (Window) win->src.window }; + return vkCreateXlibSurfaceKHR(instance, &x11, NULL, surface); +#endif +#if defined(RGFW_WAYLAND) + + VkWaylandSurfaceCreateInfoKHR wayland = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 0, 0, (struct wl_display*) _RGFW->wl_display, (struct wl_surface*) win->src.surface }; + return vkCreateWaylandSurfaceKHR(instance, &wayland, NULL, surface); +#elif defined(RGFW_WINDOWS) + VkWin32SurfaceCreateInfoKHR win32 = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 0, 0, GetModuleHandle(NULL), (HWND)win->src.window }; + + return vkCreateWin32SurfaceKHR(instance, &win32, NULL, surface); +#endif +} +#endif + +RGFW_bool RGFW_getPresentationSupport_Vulkan(VkPhysicalDevice physicalDevice, u32 queueFamilyIndex) { + if (_RGFW == NULL) RGFW_init(); +#ifdef RGFW_X11 + + Visual* visual = DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)); + RGFW_bool out = vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW->display, XVisualIDFromVisual(visual)); + return out; +#endif +#if defined(RGFW_WAYLAND) + + RGFW_bool wlout = vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW->wl_display); + return wlout; +#elif defined(RGFW_WINDOWS) + RGFW_bool out = vkGetPhysicalDeviceWin32PresentationSupportKHR(physicalDevice, queueFamilyIndex); + return out; +#elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) + RGFW_UNUSED(physicalDevice); + RGFW_UNUSED(queueFamilyIndex); + return RGFW_FALSE; /* TODO */ +#endif +} +#endif /* end of RGFW_vulkan */ + +/* +This is where OS specific stuff starts +*/ + +/* start of unix (wayland or X11 (unix) ) defines */ + +#ifdef RGFW_UNIX +#include +#include +#include + +void RGFW_stopCheckEvents(void) { + + _RGFW->eventWait_forceStop[2] = 1; + while (1) { + const char byte = 0; + const ssize_t result = write(_RGFW->eventWait_forceStop[1], &byte, 1); + if (result == 1 || result == -1) + break; + } +} + +RGFWDEF u64 RGFW_linux_getTimeNS(void); +u64 RGFW_linux_getTimeNS(void) { + struct timespec ts; + const u64 scale_factor = 1000000000; + clock_gettime(_RGFW->clock, &ts); + return (u64)ts.tv_sec * scale_factor + (u64)ts.tv_nsec; +} + +void RGFW_waitForEvent(i32 waitMS) { + if (waitMS == 0) return; + + if (_RGFW->eventWait_forceStop[0] == 0 || _RGFW->eventWait_forceStop[1] == 0) { + if (pipe(_RGFW->eventWait_forceStop) != -1) { + fcntl(_RGFW->eventWait_forceStop[0], F_GETFL, 0); + fcntl(_RGFW->eventWait_forceStop[0], F_GETFD, 0); + fcntl(_RGFW->eventWait_forceStop[1], F_GETFL, 0); + fcntl(_RGFW->eventWait_forceStop[1], F_GETFD, 0); + } + } + + struct pollfd fds[2]; + fds[0].fd = 0; + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = _RGFW->eventWait_forceStop[0]; + fds[1].events = POLLIN; + fds[1].revents = 0; + + + if (RGFW_usingWayland()) { + #ifdef RGFW_WAYLAND + fds[0].fd = wl_display_get_fd(_RGFW->wl_display); + + /* empty the queue */ + while (wl_display_prepare_read(_RGFW->wl_display) != 0) { + /* error occured when dispatching the queue */ + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; + } + } + + /* send any pending requests to the compositor */ + while (wl_display_flush(_RGFW->wl_display) == -1) { + + /* queue is full dispatch them */ + if (errno == EAGAIN) { + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; + } + } else { + return; + } + } + #endif + } else { + #ifdef RGFW_X11 + fds[0].fd = ConnectionNumber(_RGFW->display); + #endif + } + + + u64 start = RGFW_linux_getTimeNS(); + if (RGFW_usingWayland()) { + #ifdef RGFW_WAYLAND + while (wl_display_dispatch_pending(_RGFW->wl_display) == 0) { + if (poll(fds, 1, waitMS) <= 0) { + wl_display_cancel_read(_RGFW->wl_display); + break; + } else { + if (wl_display_read_events(_RGFW->wl_display) == -1) + return; + } + + if (waitMS != RGFW_eventWaitNext) { + waitMS -= (i32)(RGFW_linux_getTimeNS() - start) / (i32)1e+6; + } + } + + /* queue contains events from read, dispatch them */ + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; + } + #endif + } else { + #ifdef RGFW_X11 + while (XPending(_RGFW->display) == 0) { + if (poll(fds, 1, waitMS) <= 0) + break; + + if (waitMS != RGFW_eventWaitNext) { + waitMS -= (i32)(RGFW_linux_getTimeNS() - start) / (i32)1e+6; + } + } + #endif + } + + /* drain any data in the stop request */ + if (_RGFW->eventWait_forceStop[2]) { + char data[64]; + RGFW_MEMSET(data, 0, sizeof(data)); + (void)!read(_RGFW->eventWait_forceStop[0], data, sizeof(data)); + + _RGFW->eventWait_forceStop[2] = 0; + } +} + +char* RGFW_strtok(char* str, const char* delimStr); +char* RGFW_strtok(char* str, const char* delimStr) { + static char* static_str = NULL; + + if (str != NULL) + static_str = str; + + if (static_str == NULL) { + return NULL; + } + + while (*static_str != '\0') { + RGFW_bool delim = 0; + const char* d; + for (d = delimStr; *d != '\0'; d++) { + if (*static_str == *d) { + delim = 1; + break; + } + } + if (!delim) + break; + static_str++; + } + + if (*static_str == '\0') + return NULL; + + char* token_start = static_str; + while (*static_str != '\0') { + int delim = 0; + const char* d; + for (d = delimStr; *d != '\0'; d++) { + if (*static_str == *d) { + delim = 1; + break; + } + } + + if (delim) { + *static_str = '\0'; + static_str++; + break; + } + static_str++; + } + + return token_start; +} + +#ifdef RGFW_X11 +RGFWDEF i32 RGFW_initPlatform_X11(void); +RGFWDEF void RGFW_deinitPlatform_X11(void); +#endif +#ifdef RGFW_WAYLAND +RGFWDEF i32 RGFW_initPlatform_Wayland(void); +RGFWDEF void RGFW_deinitPlatform_Wayland(void); +#endif + +RGFWDEF void RGFW_load_X11(void); +RGFWDEF void RGFW_load_Wayland(void); + +#if !defined(RGFW_X11) || !defined(RGFW_WAYLAND) +void RGFW_load_X11(void) { } +void RGFW_load_Wayland(void) { } +#endif + +/* + * Sadly we have to use magic linux keycodes + * We can't use X11 functions, because that breaks Wayland, but they use the same keycodes so there's no use redeffing them + * We can't use linux enums, because the headers don't exist on BSD + */ +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[49] = RGFW_backtick; + _RGFW->keycodes[19] = RGFW_0; + _RGFW->keycodes[10] = RGFW_1; + _RGFW->keycodes[11] = RGFW_2; + _RGFW->keycodes[12] = RGFW_3; + _RGFW->keycodes[13] = RGFW_4; + _RGFW->keycodes[14] = RGFW_5; + _RGFW->keycodes[15] = RGFW_6; + _RGFW->keycodes[16] = RGFW_7; + _RGFW->keycodes[17] = RGFW_8; + _RGFW->keycodes[18] = RGFW_9; + _RGFW->keycodes[65] = RGFW_space; + _RGFW->keycodes[38] = RGFW_a; + _RGFW->keycodes[56] = RGFW_b; + _RGFW->keycodes[54] = RGFW_c; + _RGFW->keycodes[40] = RGFW_d; + _RGFW->keycodes[26] = RGFW_e; + _RGFW->keycodes[41] = RGFW_f; + _RGFW->keycodes[42] = RGFW_g; + _RGFW->keycodes[43] = RGFW_h; + _RGFW->keycodes[31] = RGFW_i; + _RGFW->keycodes[44] = RGFW_j; + _RGFW->keycodes[45] = RGFW_k; + _RGFW->keycodes[46] = RGFW_l; + _RGFW->keycodes[58] = RGFW_m; + _RGFW->keycodes[57] = RGFW_n; + _RGFW->keycodes[32] = RGFW_o; + _RGFW->keycodes[33] = RGFW_p; + _RGFW->keycodes[24] = RGFW_q; + _RGFW->keycodes[27] = RGFW_r; + _RGFW->keycodes[39] = RGFW_s; + _RGFW->keycodes[28] = RGFW_t; + _RGFW->keycodes[30] = RGFW_u; + _RGFW->keycodes[55] = RGFW_v; + _RGFW->keycodes[25] = RGFW_w; + _RGFW->keycodes[53] = RGFW_x; + _RGFW->keycodes[29] = RGFW_y; + _RGFW->keycodes[52] = RGFW_z; + _RGFW->keycodes[60] = RGFW_period; + _RGFW->keycodes[59] = RGFW_comma; + _RGFW->keycodes[61] = RGFW_slash; + _RGFW->keycodes[34] = RGFW_bracket; + _RGFW->keycodes[35] = RGFW_closeBracket; + _RGFW->keycodes[47] = RGFW_semicolon; + _RGFW->keycodes[48] = RGFW_apostrophe; + _RGFW->keycodes[51] = RGFW_backSlash; + _RGFW->keycodes[36] = RGFW_return; + _RGFW->keycodes[119] = RGFW_delete; + _RGFW->keycodes[77] = RGFW_numLock; + _RGFW->keycodes[106] = RGFW_kpSlash; + _RGFW->keycodes[63] = RGFW_kpMultiply; + _RGFW->keycodes[86] = RGFW_kpPlus; + _RGFW->keycodes[82] = RGFW_kpMinus; + _RGFW->keycodes[87] = RGFW_kp1; + _RGFW->keycodes[88] = RGFW_kp2; + _RGFW->keycodes[89] = RGFW_kp3; + _RGFW->keycodes[83] = RGFW_kp4; + _RGFW->keycodes[84] = RGFW_kp5; + _RGFW->keycodes[85] = RGFW_kp6; + _RGFW->keycodes[81] = RGFW_kp9; + _RGFW->keycodes[90] = RGFW_kp0; + _RGFW->keycodes[91] = RGFW_kpPeriod; + _RGFW->keycodes[104] = RGFW_kpReturn; + _RGFW->keycodes[20] = RGFW_minus; + _RGFW->keycodes[21] = RGFW_equals; + _RGFW->keycodes[22] = RGFW_backSpace; + _RGFW->keycodes[23] = RGFW_tab; + _RGFW->keycodes[66] = RGFW_capsLock; + _RGFW->keycodes[50] = RGFW_shiftL; + _RGFW->keycodes[37] = RGFW_controlL; + _RGFW->keycodes[64] = RGFW_altL; + _RGFW->keycodes[133] = RGFW_superL; + _RGFW->keycodes[105] = RGFW_controlR; + _RGFW->keycodes[134] = RGFW_superR; + _RGFW->keycodes[62] = RGFW_shiftR; + _RGFW->keycodes[108] = RGFW_altR; + _RGFW->keycodes[67] = RGFW_F1; + _RGFW->keycodes[68] = RGFW_F2; + _RGFW->keycodes[69] = RGFW_F3; + _RGFW->keycodes[70] = RGFW_F4; + _RGFW->keycodes[71] = RGFW_F5; + _RGFW->keycodes[72] = RGFW_F6; + _RGFW->keycodes[73] = RGFW_F7; + _RGFW->keycodes[74] = RGFW_F8; + _RGFW->keycodes[75] = RGFW_F9; + _RGFW->keycodes[76] = RGFW_F10; + _RGFW->keycodes[95] = RGFW_F11; + _RGFW->keycodes[96] = RGFW_F12; + _RGFW->keycodes[111] = RGFW_up; + _RGFW->keycodes[116] = RGFW_down; + _RGFW->keycodes[113] = RGFW_left; + _RGFW->keycodes[114] = RGFW_right; + _RGFW->keycodes[118] = RGFW_insert; + _RGFW->keycodes[115] = RGFW_end; + _RGFW->keycodes[112] = RGFW_pageUp; + _RGFW->keycodes[117] = RGFW_pageDown; + _RGFW->keycodes[9] = RGFW_escape; + _RGFW->keycodes[110] = RGFW_home; + _RGFW->keycodes[78] = RGFW_scrollLock; + _RGFW->keycodes[107] = RGFW_printScreen; + _RGFW->keycodes[128] = RGFW_pause; + _RGFW->keycodes[191] = RGFW_F13; + _RGFW->keycodes[192] = RGFW_F14; + _RGFW->keycodes[193] = RGFW_F15; + _RGFW->keycodes[194] = RGFW_F16; + _RGFW->keycodes[195] = RGFW_F17; + _RGFW->keycodes[196] = RGFW_F18; + _RGFW->keycodes[197] = RGFW_F19; + _RGFW->keycodes[198] = RGFW_F20; + _RGFW->keycodes[199] = RGFW_F21; + _RGFW->keycodes[200] = RGFW_F22; + _RGFW->keycodes[201] = RGFW_F23; + _RGFW->keycodes[202] = RGFW_F24; + _RGFW->keycodes[203] = RGFW_F25; + _RGFW->keycodes[142] = RGFW_kpEqual; + _RGFW->keycodes[161] = RGFW_world1; /* non-US key #1 */ + _RGFW->keycodes[162] = RGFW_world2; /* non-US key #2 */ +} + +i32 RGFW_initPlatform(void) { + #if defined(_POSIX_MONOTONIC_CLOCK) + struct timespec ts; + RGFW_MEMSET(&ts, 0, sizeof(struct timespec)); + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + _RGFW->clock = CLOCK_MONOTONIC; + #else + _RGFW->clock = CLOCK_REALTIME; + #endif + +#ifdef RGFW_WAYLAND + RGFW_load_Wayland(); + i32 ret = RGFW_initPlatform_Wayland(); + if (ret == 0) { + return 0; + } else { + #ifdef RGFW_X11 + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, "Falling back to X11"); + RGFW_useWayland(0); + #else + return ret; + #endif + } +#endif +#ifdef RGFW_X11 + RGFW_load_X11(); + return RGFW_initPlatform_X11(); +#else + return 0; +#endif +} + + +void RGFW_deinitPlatform(void) { + if (_RGFW->eventWait_forceStop[0] || _RGFW->eventWait_forceStop[1]){ + close(_RGFW->eventWait_forceStop[0]); + close(_RGFW->eventWait_forceStop[1]); + } +#ifdef RGFW_WAYLAND + if (_RGFW->useWaylandBool) { + RGFW_deinitPlatform_Wayland(); + return; + } +#endif +#ifdef RGFW_X11 + RGFW_deinitPlatform_X11(); +#endif +} + +RGFWDEF size_t RGFW_unix_stringlen(const char* name); +size_t RGFW_unix_stringlen(const char* name) { + size_t i = 0; + while (name[i]) { i++; } + return i; +} + +#endif /* end of wayland or X11 defines */ + + +/* + + +Start of Linux / Unix defines + + +*/ + +#ifdef RGFW_X11 +#ifdef RGFW_WAYLAND +#define RGFW_FUNC(func) func##_X11 +#else +#define RGFW_FUNC(func) func +#endif + +#include +#include + +#include /* for data limits (mainly used in drag and drop functions) */ +#include + +void RGFW_setXInstName(const char* name) { _RGFW->instName = name; } +#if !defined(RGFW_NO_X11_CURSOR) && defined(RGFW_X11) + #include +#endif + +#include +#include +#include + +#include /* for converting keycode to string */ +#include /* for hiding */ +#include +#include +#include + +#ifdef RGFW_OPENGL + #ifndef __gl_h_ + #define __gl_h_ + #define RGFW_gl_ndef + #define GLubyte unsigned char + #define GLenum unsigned int + #define GLint int + #define GLuint unsigned int + #define GLsizei int + #define GLfloat float + #define GLvoid void + #define GLbitfield unsigned int + #define GLintptr ptrdiff_t + #define GLsizeiptr ptrdiff_t + #define GLboolean unsigned char + #endif + + #include /* GLX defs, xlib.h, gl.h */ + #ifndef GLX_MESA_swap_control + #define GLX_MESA_swap_control + #endif + + #ifdef RGFW_gl_ndef + #undef __gl_h_ + #undef GLubyte + #undef GLenum + #undef GLint + #undef GLuint + #undef GLsizei + #undef GLfloat + #undef GLvoid + #undef GLbitfield + #undef GLintptr + #undef GLsizeiptr + #undef GLboolean + #endif + typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); +#endif + +/* atoms needed for drag and drop */ +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); + typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); + typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); +#endif + +#if !defined(RGFW_NO_X11_XI_PRELOAD) + typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); + PFN_XISelectEvents XISelectEventsSRC = NULL; + #define XISelectEvents XISelectEventsSRC + + void* X11Xihandle = NULL; +#endif + +#if !defined(RGFW_NO_X11_EXT_PRELOAD) + typedef void (* PFN_XSyncIntToValue)(XSyncValue*, int); + PFN_XSyncIntToValue XSyncIntToValueSRC = NULL; + #define XSyncIntToValue XSyncIntToValueSRC + + typedef Status (* PFN_XSyncSetCounter)(Display*, XSyncCounter, XSyncValue); + PFN_XSyncSetCounter XSyncSetCounterSRC = NULL; + #define XSyncSetCounter XSyncSetCounterSRC + + typedef XSyncCounter (* PFN_XSyncCreateCounter)(Display*, XSyncValue); + PFN_XSyncCreateCounter XSyncCreateCounterSRC = NULL; + #define XSyncCreateCounter XSyncCreateCounterSRC + + typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); + PFN_XShapeCombineMask XShapeCombineMaskSRC; + #define XShapeCombineMask XShapeCombineMaskSRC + + typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); + PFN_XShapeCombineRegion XShapeCombineRegionSRC; + #define XShapeCombineRegion XShapeCombineRegionSRC + void* X11XEXThandle = NULL; +#endif + +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; + PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; + PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; + + #define XcursorImageLoadCursor XcursorImageLoadCursorSRC + #define XcursorImageCreate XcursorImageCreateSRC + #define XcursorImageDestroy XcursorImageDestroySRC + + void* X11Cursorhandle = NULL; +#endif + +RGFWDEF RGFW_bool RGFW_waitForShowEvent_X11(RGFW_window* win); +RGFW_bool RGFW_waitForShowEvent_X11(RGFW_window* win) { + XEvent dummy; + while (!XCheckTypedWindowEvent(_RGFW->display, win->src.window, VisibilityNotify, &dummy)) { + RGFW_waitForEvent(100); + } + + return RGFW_TRUE; +} + +void* RGFW_getDisplay_X11(void) { return _RGFW->display; } +u64 RGFW_window_getWindow_X11(RGFW_window* win) { return (u64)win->src.window; } + +RGFWDEF RGFW_format RGFW_XImage_getFormat(XImage* image); +RGFW_format RGFW_XImage_getFormat(XImage* image) { + switch (image->bits_per_pixel) { + case 24: + if (image->red_mask == 0xFF0000 && image->green_mask == 0x00FF00 && image->blue_mask == 0x0000FF) + return RGFW_formatRGB8; + if (image->red_mask == 0x0000FF && image->green_mask == 0x00FF00 && image->blue_mask == 0xFF0000) + return RGFW_formatBGR8; + break; + case 32: + if (image->red_mask == 0x00FF0000 && image->green_mask == 0x0000FF00 && image->blue_mask == 0x000000FF) + return RGFW_formatBGRA8; + if (image->red_mask == 0x000000FF && image->green_mask == 0x0000FF00 && image->blue_mask == 0x00FF0000) + return RGFW_formatRGBA8; + if (image->red_mask == 0x0000FF00 && image->green_mask == 0x00FF0000 && image->blue_mask == 0xFF000000) + return RGFW_formatABGR8; + if (image->red_mask == 0x00FF0000 && image->green_mask == 0x0000FF00 && image->blue_mask == 0x000000FF) + return RGFW_formatARGB8; /* ambiguous without alpha */ + break; + } + return RGFW_formatARGB8; +} + +RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + + XWindowAttributes attrs; + if (XGetWindowAttributes(_RGFW->display, win->src.window, &attrs) == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to get window attributes."); + return RGFW_FALSE; + } + + surface->native.bitmap = XCreateImage(_RGFW->display, attrs.visual, (u32)attrs.depth, + ZPixmap, 0, NULL, (u32)surface->w, (u32)surface->h, 32, 0); + + surface->native.buffer = (u8*)RGFW_ALLOC((size_t)(w * h * 4)); + surface->native.format = RGFW_XImage_getFormat(surface->native.bitmap); + + if (surface->native.bitmap == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create XImage."); + return RGFW_FALSE; + } + + surface->native.format = RGFW_formatBGRA8; + return RGFW_TRUE; +} + +RGFW_bool RGFW_FUNC(RGFW_createSurfacePtr) (u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + return RGFW_window_createSurfacePtr(_RGFW->root, data, w, h, format, surface); +} + +void RGFW_FUNC(RGFW_window_blitSurface) (RGFW_window* win, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->native.bitmap->data = (char*)surface->native.buffer; + RGFW_copyImageData((u8*)surface->native.buffer, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); + + XPutImage(_RGFW->display, win->src.window, win->src.gc, surface->native.bitmap, 0, 0, 0, 0, (u32)RGFW_MIN(win->w, surface->w), (u32)RGFW_MIN(win->h, surface->h)); + surface->native.bitmap->data = NULL; + return; +} + +void RGFW_FUNC(RGFW_surface_freePtr) (RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + RGFW_FREE(surface->native.buffer); + XDestroyImage(surface->native.bitmap); + return; +} + +#define RGFW_LOAD_ATOM(name) \ + static Atom name = 0; \ + if (name == 0) name = XInternAtom(_RGFW->display, #name, False); + +void RGFW_FUNC(RGFW_window_setBorder) (RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + RGFW_LOAD_ATOM(_MOTIF_WM_HINTS); + + struct __x11WindowHints { + unsigned long flags, functions, decorations, status; + long input_mode; + } hints; + hints.flags = 2; + hints.decorations = border; + + XChangeProperty(_RGFW->display, win->src.window, _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, PropModeReplace, (u8*)&hints, 5); + + if (RGFW_window_isHidden(win) == 0) { + RGFW_window_hide(win); + RGFW_window_show(win); + } +} + +void RGFW_FUNC(RGFW_window_setRawMouseModePlatform) (RGFW_window* win, RGFW_bool state) { + RGFW_UNUSED(win); RGFW_UNUSED(state); +} + +void RGFW_FUNC(RGFW_window_captureMousePlatform) (RGFW_window* win, RGFW_bool state) { + if (state) { + unsigned int event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + XGrabPointer(_RGFW->display, win->src.window, True, event_mask, GrabModeAsync, GrabModeAsync, win->src.window, None, CurrentTime); + } else { + XUngrabPointer(_RGFW->display, CurrentTime); + } +} + +#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = dlopen(lib, RTLD_LAZY | RTLD_LOCAL) +#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ + void* ptr = dlsym(proc, #name); \ + if (ptr != NULL) RGFW_MEMCPY(&name##SRC, &ptr, sizeof(PFN_##name)); \ +} + +RGFWDEF void RGFW_window_getVisual(XVisualInfo* visual, RGFW_bool transparent); +void RGFW_window_getVisual(XVisualInfo* visual, RGFW_bool transparent) { + visual->visual = DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)); + visual->depth = DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)); + if (transparent) { + XMatchVisualInfo(_RGFW->display, DefaultScreen(_RGFW->display), 32, TrueColor, visual); /*!< for RGBA backgrounds */ + if (visual->depth != 32) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to load a 32-bit depth."); + } +} + +RGFWDEF int RGFW_XErrorHandler(Display* display, XErrorEvent* ev); +int RGFW_XErrorHandler(Display* display, XErrorEvent* ev) { + char errorText[512]; + XGetErrorText(display, ev->error_code, errorText, sizeof(errorText)); + + char buf[1024]; + RGFW_SNPRINTF(buf, sizeof(buf), "[X Error] %s\n Error code: %d\n Request code: %d\n Minor code: %d\n Serial: %lu\n", + errorText, + ev->error_code, ev->request_code, ev->minor_code, ev->serial); + + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errX11, buf); + _RGFW->x11Error = ev; + return 0; +} + +void RGFW_XCreateWindow (XVisualInfo visual, const char* name, RGFW_windowFlags flags, RGFW_window* win) { + i64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | + LeaveWindowMask | EnterWindowMask | ExposureMask | VisibilityChangeMask | PropertyChangeMask; + + /* make X window attrubutes */ + XSetWindowAttributes swa; + RGFW_MEMSET(&swa, 0, sizeof(swa)); + + win->src.parent = DefaultRootWindow(_RGFW->display); + + Colormap cmap; + swa.colormap = cmap = XCreateColormap(_RGFW->display, + win->src.parent, + visual.visual, AllocNone); + swa.event_mask = event_mask; + swa.background_pixmap = None; + + /* create the window */ + win->src.window = XCreateWindow(_RGFW->display, win->src.parent, win->x, win->y, (u32)win->w, (u32)win->h, + 0, visual.depth, InputOutput, visual.visual, + CWBorderPixel | CWColormap | CWEventMask, &swa); + + win->src.flashEnd = 0; + + XFreeColors(_RGFW->display, cmap, NULL, 0, 0); + + XSaveContext(_RGFW->display, win->src.window, _RGFW->context, (XPointer)win); + + win->src.gc = XCreateGC(_RGFW->display, win->src.window, 0, NULL); + + /* In your .desktop app, if you set the property + StartupWMClass=RGFW that will assoicate the launcher icon + with your application - robrohan */ + if (_RGFW->className == NULL) + _RGFW->className = (char*)name; + + XClassHint hint; + hint.res_class = (char*)_RGFW->className; + + if (_RGFW->instName == NULL) hint.res_name = (char*)name; + else hint.res_name = (char*)_RGFW->instName; + + XSetClassHint(_RGFW->display, win->src.window, &hint); + + XWMHints hints; + hints.flags = StateHint; + hints.initial_state = NormalState; + + XSetWMHints(_RGFW->display, win->src.window, &hints); + + if (flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); + + XSelectInput(_RGFW->display, (Drawable) win->src.window, event_mask); /*!< tell X11 what events we want */ + + /* make it so the user can't close the window until the program does */ + RGFW_LOAD_ATOM(WM_DELETE_WINDOW); + XSetWMProtocols(_RGFW->display, (Drawable) win->src.window, &WM_DELETE_WINDOW, 1); + /* set the background */ + RGFW_window_setName(win, name); + + XMoveWindow(_RGFW->display, (Drawable) win->src.window, win->x, win->y); /*!< move the window to it's proper cords */ + + if (flags & RGFW_windowAllowDND) { /* init drag and drop atoms and turn on drag and drop for this window */ + win->internal.flags |= RGFW_windowAllowDND; + + /* actions */ + Atom XdndAware = XInternAtom(_RGFW->display, "XdndAware", False); + const u8 version = 5; + + XChangeProperty(_RGFW->display, win->src.window, + XdndAware, 4, 32, + PropModeReplace, &version, 1); /*!< turns on drag and drop */ + } + +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST_COUNTER) + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST) + + Atom protcols[2] = {_NET_WM_SYNC_REQUEST, WM_DELETE_WINDOW}; + XSetWMProtocols(_RGFW->display, win->src.window, protcols, 2); + + XSyncValue initial_value; + XSyncIntToValue(&initial_value, 0); + win->src.counter = XSyncCreateCounter(_RGFW->display, initial_value); + + XChangeProperty(_RGFW->display, win->src.window, _NET_WM_SYNC_REQUEST_COUNTER, XA_CARDINAL, 32, PropModeReplace, (u8*)&win->src.counter, 1); +#endif + + win->src.x = win->x; + win->src.y = win->y; + win->src.w = win->w; + win->src.h = win->h; + + XSetWindowBackground(_RGFW->display, win->src.window, None); + XClearWindow(_RGFW->display, win->src.window); + + /* stupid hack to make resizing the window less bad */ + XSetWindowBackgroundPixmap(_RGFW->display, win->src.window, None); +} + +RGFW_window* RGFW_FUNC(RGFW_createWindowPlatform) (const char* name, RGFW_windowFlags flags, RGFW_window* win) { + if ((flags & RGFW_windowOpenGL) || (flags & RGFW_windowEGL)) { + win->src.window = 0; + return win; + } + + XVisualInfo visual; + RGFW_window_getVisual(&visual, RGFW_BOOL(win->internal.flags & RGFW_windowTransparent)); + RGFW_XCreateWindow(visual, name, flags, win); + return win; /*return newly created window */ +} + +RGFW_bool RGFW_FUNC(RGFW_getGlobalMouse) (i32* fX, i32* fY) { + RGFW_init(); + i32 x, y; + u32 z; + Window window1, window2; + XQueryPointer(_RGFW->display, XDefaultRootWindow(_RGFW->display), &window1, &window2, fX, fY, &x, &y, &z); + return RGFW_TRUE; +} + +RGFWDEF void RGFW_XHandleClipboardSelection(XEvent* event); +void RGFW_XHandleClipboardSelection(XEvent* event) { RGFW_UNUSED(event); + RGFW_LOAD_ATOM(ATOM_PAIR); + RGFW_LOAD_ATOM(MULTIPLE); + RGFW_LOAD_ATOM(TARGETS); + RGFW_LOAD_ATOM(SAVE_TARGETS); + RGFW_LOAD_ATOM(UTF8_STRING); + + const XSelectionRequestEvent* request = &event->xselectionrequest; + Atom formats[2] = {0}; + formats[0] = UTF8_STRING; + formats[1] = XA_STRING; + const int formatCount = sizeof(formats) / sizeof(formats[0]); + + if (request->target == TARGETS) { + Atom targets[4] = {0}; + targets[0] = TARGETS; + targets[1] = MULTIPLE; + targets[2] = UTF8_STRING; + targets[3] = XA_STRING; + + XChangeProperty(_RGFW->display, request->requestor, request->property, + XA_ATOM, 32, PropModeReplace, (u8*) targets, sizeof(targets) / sizeof(Atom)); + } else if (request->target == MULTIPLE) { + Atom* targets = NULL; + + Atom actualType = 0; + int actualFormat = 0; + unsigned long count = 0, bytesAfter = 0; + + XGetWindowProperty(_RGFW->display, request->requestor, request->property, 0, LONG_MAX, + False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); + + unsigned long i; + for (i = 0; i < (u32)count; i += 2) { + if (targets[i] == UTF8_STRING || targets[i] == XA_STRING) + XChangeProperty(_RGFW->display, request->requestor, targets[i + 1], targets[i], + 8, PropModeReplace, (const unsigned char *)_RGFW->clipboard, (i32)_RGFW->clipboard_len); + else + targets[i + 1] = None; + } + + XChangeProperty(_RGFW->display, + request->requestor, request->property, ATOM_PAIR, 32, + PropModeReplace, (u8*) targets, (i32)count); + + XFlush(_RGFW->display); + XFree(targets); + } else if (request->target == SAVE_TARGETS) + XChangeProperty(_RGFW->display, request->requestor, request->property, 0, 32, PropModeReplace, NULL, 0); + else { + int i; + for (i = 0; i < formatCount; i++) { + if (request->target != formats[i]) + continue; + XChangeProperty(_RGFW->display, request->requestor, request->property, request->target, + 8, PropModeReplace, (u8*) _RGFW->clipboard, (i32)_RGFW->clipboard_len); + } + } + + XEvent reply = { SelectionNotify }; + reply.xselection.property = request->property; + reply.xselection.display = request->display; + reply.xselection.requestor = request->requestor; + reply.xselection.selection = request->selection; + reply.xselection.target = request->target; + reply.xselection.time = request->time; + + XSendEvent(_RGFW->display, request->requestor, False, 0, &reply); + XFlush(_RGFW->display); +} + +i32 RGFW_XHandleClipboardSelectionHelper(void); + +u8 RGFW_FUNC(RGFW_rgfwToKeyChar) (u32 key) { + u32 keycode = RGFW_rgfwToApiKey(key); + + Window root = DefaultRootWindow(_RGFW->display); + Window ret_root, ret_child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + XQueryPointer(_RGFW->display, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); + KeySym sym = (KeySym)XkbKeycodeToKeysym(_RGFW->display, (KeyCode)keycode, 0, (KeyCode)mask & ShiftMask ? 1 : 0); + + if ((mask & LockMask) && sym >= XK_a && sym <= XK_z) + sym = (mask & ShiftMask) ? sym + 32 : sym - 32; + if ((u8)sym != (u32)sym) + sym = 0; + + return (u8)sym; +} + +RGFWDEF void RGFW_XHandleEvent(void); +void RGFW_XHandleEvent(void) { + RGFW_LOAD_ATOM(XdndTypeList); + RGFW_LOAD_ATOM(XdndSelection); + RGFW_LOAD_ATOM(XdndEnter); + RGFW_LOAD_ATOM(XdndPosition); + RGFW_LOAD_ATOM(XdndStatus); + RGFW_LOAD_ATOM(XdndLeave); + RGFW_LOAD_ATOM(XdndDrop); + RGFW_LOAD_ATOM(XdndFinished); + RGFW_LOAD_ATOM(XdndActionCopy); + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST); + RGFW_LOAD_ATOM(WM_PROTOCOLS); + RGFW_LOAD_ATOM(WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE); + + /* xdnd data */ + static Window source = 0; + static long version = 0; + static i32 format = 0; + + static float deltaX = 0.0f; + static float deltaY = 0.0f; + + XEvent reply = { ClientMessage }; + XEvent E; + + XNextEvent(_RGFW->display, &E); + + if (E.type != GenericEvent) { + deltaX = 0.0f; + deltaY = 0.0f; + } + +#ifndef RGFW_NO_XRANDR + if (E.type == _RGFW->xrandrEventBase + RRNotify) { + RGFW_pollMonitors(); + return; + } +#endif + + switch (E.type) { + case SelectionRequest: + RGFW_XHandleClipboardSelection(&E); + return; + case GenericEvent: { + XGetEventData(_RGFW->display, &E.xcookie); + switch (E.xcookie.evtype) { + case XI_RawMotion: { + XIRawEvent* raw = (XIRawEvent *)E.xcookie.data; + if (raw->valuators.mask_len == 0) { + XFreeEventData(_RGFW->display, &E.xcookie); + return; + } + + i32 index = 0; + if (XIMaskIsSet(raw->valuators.mask, 0) != 0) { + deltaX += (float)raw->raw_values[index]; + index += 1; + } + + if (XIMaskIsSet(raw->valuators.mask, 1) != 0) + deltaY += (float)raw->raw_values[index]; + + _RGFW->vectorX = (float)deltaX; + _RGFW->vectorY = (float)deltaY; + } + default: break; + } + + XFreeEventData(_RGFW->display, &E.xcookie); + return; + } + } + + RGFW_window* win = NULL; + if (XFindContext(_RGFW->display, E.xany.window, _RGFW->context, (XPointer*) &win) != 0) { + return; + } + + if (win->src.flashEnd) { + if ((win->src.flashEnd <= RGFW_linux_getTimeNS()) || RGFW_window_isInFocus(win)) { + RGFW_window_flash(win, RGFW_flashCancel); + } + } + + + /* + Repeated key presses are sent as a release followed by another press at the same time. + We want to convert that into a single key press event with the repeat flag set + */ + + RGFW_bool keyRepeat = RGFW_FALSE; + + if (E.type == KeyRelease && XEventsQueued(_RGFW->display, QueuedAfterReading)) { + XEvent NE; + XPeekEvent(_RGFW->display, &NE); + if (NE.type == KeyPress && E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) { + /* Use the next KeyPress event */ + XNextEvent(_RGFW->display, &E); + keyRepeat = RGFW_TRUE; + } + } + + switch (E.type) { + case KeyPress: { + if (!(win->internal.enabledEvents & RGFW_keyPressedFlag)) return; + RGFW_key value = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); + u8 sym = (u8)RGFW_rgfwToKeyChar(value); + + XkbStateRec state; + XkbGetState(_RGFW->display, XkbUseCoreKbd, &state); + RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); + + RGFW_keyCallback(win, value, sym, win->internal.mod, keyRepeat, RGFW_TRUE); + break; + } + case KeyRelease: { + if (!(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return; + + RGFW_key value = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); + u8 sym = (u8)RGFW_rgfwToKeyChar(value); + + XkbStateRec state; + XkbGetState(_RGFW->display, XkbUseCoreKbd, &state); + RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); + + RGFW_keyCallback(win, value, sym, win->internal.mod, keyRepeat, RGFW_FALSE); + break; + } + case ButtonPress: { + RGFW_bool scroll = RGFW_FALSE; + if (E.xbutton.button >= Button4 && E.xbutton.button <= 7) { + scroll = RGFW_TRUE; + } + + float scrollX = 0.0f; + float scrollY = 0.0f; + RGFW_mouseButton value = 0; + + switch (E.xbutton.button) { + case Button1: value = RGFW_mouseLeft; break; + case Button2: value = RGFW_mouseMiddle; break; + case Button3: value = RGFW_mouseRight; break; + case Button4: scrollY = 1.0; break; + case Button5: scrollY = -1.0; break; + case 6: scrollX = 1.0f; break; + case 7: scrollX = -1.0f; break; + default: + value = (u8)E.xbutton.button - Button1 - 4; + break; + } + + if (scroll) { + RGFW_mouseScrollCallback(win, scrollX, scrollY); + break; + } + + RGFW_mouseButtonCallback(win, value, RGFW_TRUE); + break; + } + case ButtonRelease: { + if (E.xbutton.button >= Button4 && E.xbutton.button <= 7) break; + + RGFW_mouseButton value = 0; + switch(E.xbutton.button) { + case Button1: value = RGFW_mouseLeft; break; + case Button2: value = RGFW_mouseMiddle; break; + case Button3: value = RGFW_mouseRight; break; + default: + value = (u8)E.xbutton.button - Button1 - 4; + break; + } + + RGFW_mouseButtonCallback(win, value, RGFW_FALSE); + break; + } + case MotionNotify: + RGFW_mousePosCallback(win, E.xmotion.x, E.xmotion.y, _RGFW->vectorX, _RGFW->vectorY); + break; + + case Expose: { + RGFW_windowRefreshCallback(win); + +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(_RGFW->display, win->src.counter, value); +#endif + break; + } + + case PropertyNotify: + if (E.xproperty.state != PropertyNewValue) break; + + if (E.xproperty.atom == WM_STATE) { + if (RGFW_window_isMinimized(win) && !(win->internal.flags & RGFW_windowMinimized)) { + RGFW_windowMinimizedCallback(win); + break; + } + } else if (E.xproperty.atom == _NET_WM_STATE) { + if (RGFW_window_isMaximized(win) && !(win->internal.flags & RGFW_windowMaximize)) { + RGFW_windowMaximizedCallback(win, win->x, win->y, win->w, win->h); + break; + } + } + + RGFW_window_checkMode(win); + break; + case MapNotify: case UnmapNotify: RGFW_window_checkMode(win); break; + case ClientMessage: { + RGFW_LOAD_ATOM(WM_DELETE_WINDOW); + /* if the client closed the window */ + if (E.xclient.data.l[0] == (long)WM_DELETE_WINDOW) { + RGFW_windowQuitCallback(win); + break; + } +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + if (E.xclient.message_type == WM_PROTOCOLS && (Atom)E.xclient.data.l[0] == _NET_WM_SYNC_REQUEST) { + RGFW_windowRefreshCallback(win); + win->src.counter_value = 0; + win->src.counter_value |= E.xclient.data.l[2]; + win->src.counter_value |= (E.xclient.data.l[3] << 32); + + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(_RGFW->display, win->src.counter, value); + break; + } +#endif + if ((win->internal.flags & RGFW_windowAllowDND) == 0) + return; + + i32 dragX = 0; + i32 dragY = 0; + + reply.xclient.window = source; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)win->src.window; + reply.xclient.data.l[1] = 0; + reply.xclient.data.l[2] = None; + + if (E.xclient.message_type == XdndEnter) { + if (version > 5) + break; + + unsigned long count; + Atom* formats; + Atom real_formats[6]; + Bool list = E.xclient.data.l[1] & 1; + + source = (unsigned long int)E.xclient.data.l[0]; + version = E.xclient.data.l[1] >> 24; + format = None; + if (list) { + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; + + XGetWindowProperty( + _RGFW->display, source, XdndTypeList, + 0, LONG_MAX, False, 4, + &actualType, &actualFormat, &count, &bytesAfter, (u8**)&formats + ); + } else { + count = 0; + + size_t i; + for (i = 2; i < 5; i++) { + if (E.xclient.data.l[i] != None) { + real_formats[count] = (unsigned long int)E.xclient.data.l[i]; + count += 1; + } + } + + formats = real_formats; + } + + Atom XtextPlain = XInternAtom(_RGFW->display, "text/plain", False); + Atom XtextUriList = XInternAtom(_RGFW->display, "text/uri-list", False); + + size_t i; + for (i = 0; i < count; i++) { + if (formats[i] == XtextUriList || formats[i] == XtextPlain) { + format = (int)formats[i]; + break; + } + } + + if (list) { + XFree(formats); + } + + break; + } + + if (E.xclient.message_type == XdndPosition) { + const i32 xabs = (E.xclient.data.l[2] >> 16) & 0xffff; + const i32 yabs = (E.xclient.data.l[2]) & 0xffff; + Window dummy; + i32 xpos, ypos; + + if (version > 5) + break; + + XTranslateCoordinates( + _RGFW->display, XDefaultRootWindow(_RGFW->display), win->src.window, + xabs, yabs, &xpos, &ypos, &dummy + ); + + dragX = xpos; + dragY = ypos; + + reply.xclient.window = source; + reply.xclient.message_type = XdndStatus; + + if (format) { + reply.xclient.data.l[1] = 1; + if (version >= 2) + reply.xclient.data.l[4] = (long)XdndActionCopy; + } + + XSendEvent(_RGFW->display, source, False, NoEventMask, &reply); + XFlush(_RGFW->display); + break; + } + if (E.xclient.message_type != XdndDrop) + break; + + if (version > 5) + break; + + if (format) { + Time time = (version >= 1) + ? (Time)E.xclient.data.l[2] + : CurrentTime; + + XConvertSelection( + _RGFW->display, XdndSelection, (Atom)format, + XdndSelection, win->src.window, time + ); + } else if (version >= 2) { + XEvent new_reply = { ClientMessage }; + + XSendEvent(_RGFW->display, source, False, NoEventMask, &new_reply); + XFlush(_RGFW->display); + } + + RGFW_dataDragCallback(win, dragX, dragY); + } break; + case SelectionNotify: { + /* this is only for checking for xdnd drops */ + if (!(win->internal.enabledEvents & RGFW_dataDropFlag) || E.xselection.property != XdndSelection || !(win->internal.flags & RGFW_windowAllowDND)) + return; + char* data; + unsigned long result; + + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; + + XGetWindowProperty(_RGFW->display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); + + if (result == 0) + break; + + const char* prefix = (const char*)"file://"; + + char* line; + + size_t count = 0; + char** files = _RGFW->files; + + while ((line = (char*)RGFW_strtok(data, "\r\n"))) { + char path[RGFW_MAX_PATH]; + + data = NULL; + + if (line[0] == '#') + continue; + + char* l; + for (l = line; 1; l++) { + if ((l - line) > 7) + break; + else if (*l != prefix[(l - line)]) + break; + else if (*l == '\0' && prefix[(l - line)] == '\0') { + line += 7; + while (*line != '/') + line++; + break; + } else if (*l == '\0') + break; + } + + count++; + + size_t index = 0; + while (*line) { + if (line[0] == '%' && line[1] && line[2]) { + char digits[3] = {0}; + digits[0] = line[1]; + digits[1] = line[2]; + digits[2] = '\0'; + path[index] = (char) RGFW_STRTOL(digits, NULL, 16); + line += 2; + } else + path[index] = *line; + + index++; + line++; + } + path[index] = '\0'; + RGFW_MEMCPY(files[count - 1], path, index + 1); + } + + RGFW_dataDropCallback(win, files, count); + if (data) + XFree(data); + + if (version >= 2) { + XEvent new_reply = { ClientMessage }; + new_reply.xclient.window = source; + new_reply.xclient.message_type = XdndFinished; + new_reply.xclient.format = 32; + new_reply.xclient.data.l[1] = (long int)result; + new_reply.xclient.data.l[2] = (long int)XdndActionCopy; + XSendEvent(_RGFW->display, source, False, NoEventMask, &new_reply); + XFlush(_RGFW->display); + } + break; + } + case FocusIn: + RGFW_focusCallback(win, 1); + break; + case FocusOut: + RGFW_focusCallback(win, 0); + break; + case EnterNotify: { + RGFW_mouseNotifyCallback(win, E.xcrossing.x, E.xcrossing.y, RGFW_TRUE); + break; + } + + case LeaveNotify: { + RGFW_mouseNotifyCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, RGFW_FALSE); + break; + } + case ReparentNotify: + win->src.parent = E.xreparent.parent; + break; + case ConfigureNotify: { + /* detect resize */ + if (E.xconfigure.width != win->src.w || E.xconfigure.height != win->src.h) { + RGFW_window_checkMode(win); + win->src.w = E.xconfigure.width; + win->src.h = E.xconfigure.height; + RGFW_windowResizedCallback(win, E.xconfigure.width, E.xconfigure.height); + } + + i32 x = E.xconfigure.x; + i32 y = E.xconfigure.y; + + /* + if the event came from the server and we're not a direct child of the root window then + we're using local coords which need to be translated into screen coords + */ + Window root = DefaultRootWindow(_RGFW->display); + if (E.xany.send_event == 0 && win->src.parent != root) { + Window dummy = 0; + XTranslateCoordinates(_RGFW->display, win->src.parent, root, x, y, &x, &y, &dummy); + } + + /* detect move */ + if (E.xconfigure.x != win->src.x || E.xconfigure.y != win->src.y) { + win->src.x = E.xconfigure.x; + win->src.y = E.xconfigure.y; + RGFW_windowMovedCallback(win, E.xconfigure.x, E.xconfigure.y); + } + return; + } + default: + break; + } + + XFlush(_RGFW->display); +} + +void RGFW_FUNC(RGFW_pollEvents) (void) { + RGFW_resetPrevState(); + + XPending(_RGFW->display); + /* if there is no unread queued events, get a new one */ + while (QLength(_RGFW->display)) { + RGFW_XHandleEvent(); + } +} + +void RGFW_FUNC(RGFW_window_move) (RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + win->x = x; + win->y = y; + + XMoveWindow(_RGFW->display, win->src.window, x, y); + return; +} + + +void RGFW_FUNC(RGFW_window_resize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + win->w = (i32)w; + win->h = (i32)h; + + XResizeWindow(_RGFW->display, win->src.window, (u32)w, (u32)h); + + if ((win->internal.flags & RGFW_windowNoResize)) { + XSizeHints sh; + sh.flags = (1L << 4) | (1L << 5); + sh.min_width = sh.max_width = (i32)w; + sh.min_height = sh.max_height = (i32)h; + + XSetWMSizeHints(_RGFW->display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); + } + return; +} + +void RGFW_FUNC(RGFW_window_setAspectRatio) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + + + if (w == 0 && h == 0) + return; + XSizeHints hints; + long flags; + + XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); + + hints.flags |= PAspect; + + hints.min_aspect.x = hints.max_aspect.x = (i32)w; + hints.min_aspect.y = hints.max_aspect.y = (i32)h; + + XSetWMNormalHints(_RGFW->display, win->src.window, &hints); + return; +} + +void RGFW_FUNC(RGFW_window_setMinSize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + + long flags; + XSizeHints hints; + RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); + + XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); + + hints.flags |= PMinSize; + + hints.min_width = (i32)w; + hints.min_height = (i32)h; + + XSetWMNormalHints(_RGFW->display, win->src.window, &hints); + return; +} + +void RGFW_FUNC(RGFW_window_setMaxSize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + + long flags; + XSizeHints hints; + RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); + + XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); + + hints.flags |= PMaxSize; + + hints.max_width = (i32)w; + hints.max_height = (i32)h; + + XSetWMNormalHints(_RGFW->display, win->src.window, &hints); + return; +} + +void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized); +void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); + + XEvent xev = {0}; + xev.type = ClientMessage; + xev.xclient.window = win->src.window; + xev.xclient.message_type = _NET_WM_STATE; + xev.xclient.format = 32; + xev.xclient.data.l[0] = maximized; + xev.xclient.data.l[1] = (long int)_NET_WM_STATE_MAXIMIZED_HORZ; + xev.xclient.data.l[2] = (long int)_NET_WM_STATE_MAXIMIZED_VERT; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + XSendEvent(_RGFW->display, DefaultRootWindow(_RGFW->display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); +} + +void RGFW_FUNC(RGFW_window_maximize) (RGFW_window* win) { + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + + RGFW_toggleXMaximized(win, 1); + return; +} + +void RGFW_FUNC(RGFW_window_focus) (RGFW_window* win) { + RGFW_ASSERT(win); + + XWindowAttributes attr; + XGetWindowAttributes(_RGFW->display, win->src.window, &attr); + if (attr.map_state != IsViewable) return; + + XSetInputFocus(_RGFW->display, win->src.window, RevertToPointerRoot, CurrentTime); + XFlush(_RGFW->display); +} + +void RGFW_FUNC(RGFW_window_raise) (RGFW_window* win) { + RGFW_ASSERT(win); + XMapRaised(_RGFW->display, win->src.window); + RGFW_window_setFullscreen(win, RGFW_window_isFullscreen(win)); +} + +void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen); +void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(_NET_WM_STATE); + + XEvent xev = {0}; + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.message_type = _NET_WM_STATE; + xev.xclient.window = win->src.window; + xev.xclient.format = 32; + xev.xclient.data.l[0] = fullscreen; + xev.xclient.data.l[1] = (long int)netAtom; + xev.xclient.data.l[2] = 0; + + XSendEvent(_RGFW->display, DefaultRootWindow(_RGFW->display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); +} + +void RGFW_FUNC(RGFW_window_setFullscreen)(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + + if (fullscreen) { + win->internal.flags |= RGFW_windowFullscreen; + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + } + else win->internal.flags &= ~(u32)RGFW_windowFullscreen; + + XRaiseWindow(_RGFW->display, win->src.window); + + RGFW_LOAD_ATOM(_NET_WM_STATE_FULLSCREEN); + RGFW_window_setXAtom(win, _NET_WM_STATE_FULLSCREEN, fullscreen); + + if (!(win->internal.flags & RGFW_windowTransparent)) { + const unsigned char value = fullscreen; + RGFW_LOAD_ATOM(_NET_WM_BYPASS_COMPOSITOR); + XChangeProperty( + _RGFW->display, win->src.window, + _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, + PropModeReplace, &value, 1); + } +} + +void RGFW_FUNC(RGFW_window_setFloating)(RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); + RGFW_window_setXAtom(win, _NET_WM_STATE_ABOVE, floating); +} + +void RGFW_FUNC(RGFW_window_setOpacity)(RGFW_window* win, u8 opacity) { + RGFW_ASSERT(win != NULL); + const u32 value = (u32) (0xffffffffu * (double) opacity); + RGFW_LOAD_ATOM(NET_WM_WINDOW_OPACITY); + XChangeProperty(_RGFW->display, win->src.window, + NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); +} + +void RGFW_FUNC(RGFW_window_minimize)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + if (RGFW_window_isMaximized(win)) return; + + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + XIconifyWindow(_RGFW->display, win->src.window, DefaultScreen(_RGFW->display)); + XFlush(_RGFW->display); +} + +void RGFW_FUNC(RGFW_window_restore)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_toggleXMaximized(win, RGFW_FALSE); + RGFW_window_move(win, win->internal.oldX, win->internal.oldY); + RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); + + RGFW_window_show(win); + XFlush(_RGFW->display); +} + +RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { + RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); + + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + Atom* prop_return = NULL; + + int status = XGetWindowProperty(_RGFW->display, win->src.window, _NET_WM_STATE, 0, (~0L), False, XA_ATOM, + &actual_type, &actual_format, &nitems, &bytes_after, + (unsigned char **)&prop_return); + + if (status != Success || actual_type != XA_ATOM) + return RGFW_FALSE; + + unsigned long i; + for (i = 0; i < nitems; i++) + if (prop_return[i] == _NET_WM_STATE_ABOVE) return RGFW_TRUE; + + if (prop_return) + XFree(prop_return); + return RGFW_FALSE; +} + +void RGFW_FUNC(RGFW_window_setName)(RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + if (name == NULL) name = "\0"; + + XStoreName(_RGFW->display, win->src.window, name); + + RGFW_LOAD_ATOM(_NET_WM_NAME); RGFW_LOAD_ATOM(UTF8_STRING); + + XChangeProperty( + _RGFW->display, win->src.window, _NET_WM_NAME, UTF8_STRING, + 8, PropModeReplace, (u8*)name, (int)RGFW_unix_stringlen(name) + ); +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_FUNC(RGFW_window_setMousePassthrough) (RGFW_window* win, RGFW_bool passthrough) { + RGFW_ASSERT(win != NULL); + if (passthrough) { + Region region = XCreateRegion(); + XShapeCombineRegion(_RGFW->display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); + XDestroyRegion(region); + + return; + } + + XShapeCombineMask(_RGFW->display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); +} +#endif /* RGFW_NO_PASSTHROUGH */ + +RGFW_bool RGFW_FUNC(RGFW_window_setIconEx) (RGFW_window* win, u8* data_src, i32 w, i32 h, RGFW_format format, RGFW_icon type) { + Atom _NET_WM_ICON = XInternAtom(_RGFW->display, "_NET_WM_ICON", False); + RGFW_ASSERT(win != NULL); + if (data_src == NULL) { + RGFW_bool res = (RGFW_bool)XChangeProperty( + _RGFW->display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + PropModeReplace, (u8*)NULL, 0 + ); + return res; + } + + i32 count = (i32)(2 + (w * h)); + + unsigned long* data = (unsigned long*) RGFW_ALLOC((u32)count * sizeof(unsigned long)); + RGFW_ASSERT(data != NULL); + + RGFW_MEMSET(data, 0, (u32)count * sizeof(unsigned long)); + data[0] = (unsigned long)w; + data[1] = (unsigned long)h; + + RGFW_copyImageData64((u8*)&data[2], w, h, RGFW_formatBGRA8, data_src, format, RGFW_TRUE); + RGFW_bool res = RGFW_TRUE; + if (type & RGFW_iconTaskbar) { + res = (RGFW_bool)XChangeProperty( + _RGFW->display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + PropModeReplace, (u8*)data, count + ); + } + + RGFW_copyImageData64((u8*)&data[2], w, h, RGFW_formatBGRA8, data_src, format, RGFW_FALSE); + + if (type & RGFW_iconWindow) { + XWMHints wm_hints; + wm_hints.flags = IconPixmapHint; + + i32 depth = DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)); + XImage *image = XCreateImage(_RGFW->display, DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)), + (u32)depth, ZPixmap, 0, (char *)&data[2], (u32)w, (u32)h, 32, 0); + + wm_hints.icon_pixmap = XCreatePixmap(_RGFW->display, win->src.window, (u32)w, (u32)h, (u32)depth); + XPutImage(_RGFW->display, wm_hints.icon_pixmap, DefaultGC(_RGFW->display, DefaultScreen(_RGFW->display)), image, 0, 0, 0, 0, (u32)w, (u32)h); + image->data = NULL; + XDestroyImage(image); + + XSetWMHints(_RGFW->display, win->src.window, &wm_hints); + } + + RGFW_FREE(data); + XFlush(_RGFW->display); + return RGFW_BOOL(res); +} + +RGFW_mouse* RGFW_FUNC(RGFW_loadMouse) (u8* data, i32 w, i32 h, RGFW_format format) { + RGFW_ASSERT(data); +#ifndef RGFW_NO_X11_CURSOR + RGFW_init(); + XcursorImage* native = XcursorImageCreate((i32)w, (i32)h); + native->xhot = 0; + native->yhot = 0; + RGFW_MEMSET(native->pixels, 0, (u32)(w * h * 4)); + RGFW_copyImageData((u8*)native->pixels, w, h, RGFW_formatBGRA8, data, format); + + Cursor cursor = XcursorImageLoadCursor(_RGFW->display, native); + XcursorImageDestroy(native); + + return (void*)cursor; +#else + RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); + return NULL; +#endif +} + +void RGFW_FUNC(RGFW_window_setMouse)(RGFW_window* win, RGFW_mouse* mouse) { + RGFW_ASSERT(win && mouse); + XDefineCursor(_RGFW->display, win->src.window, (Cursor)mouse); +} + +void RGFW_FUNC(RGFW_freeMouse)(RGFW_mouse* mouse) { + RGFW_ASSERT(mouse); + XFreeCursor(_RGFW->display, (Cursor)mouse); +} + +void RGFW_FUNC(RGFW_window_moveMouse)(RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + + XEvent event; + XQueryPointer(_RGFW->display, DefaultRootWindow(_RGFW->display), + &event.xbutton.root, &event.xbutton.window, + &event.xbutton.x_root, &event.xbutton.y_root, + &event.xbutton.x, &event.xbutton.y, + &event.xbutton.state); + + win->internal.lastMouseX = x - win->x; + win->internal.lastMouseY = y - win->y; + if (event.xbutton.x == x && event.xbutton.y == y) + return; + + XWarpPointer(_RGFW->display, None, win->src.window, 0, 0, 0, 0, (int) x - win->x, (int) y - win->y); +} + +RGFW_bool RGFW_FUNC(RGFW_window_setMouseDefault) (RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +RGFW_bool RGFW_FUNC(RGFW_window_setMouseStandard) (RGFW_window* win, u8 mouse) { + RGFW_ASSERT(win != NULL); + + u32 mouseIcon = 0; + + switch (mouse) { + case RGFW_mouseNormal: mouseIcon = XC_left_ptr; break; + case RGFW_mouseArrow: mouseIcon = XC_left_ptr; break; + case RGFW_mouseIbeam: mouseIcon = XC_xterm; break; + case RGFW_mouseWait: mouseIcon = XC_watch; break; + case RGFW_mouseCrosshair: mouseIcon = XC_tcross; break; + case RGFW_mouseProgress: mouseIcon = XC_watch; break; + case RGFW_mouseResizeNWSE: mouseIcon = XC_top_left_corner; break; + case RGFW_mouseResizeNESW: mouseIcon = XC_top_right_corner; break; + case RGFW_mouseResizeEW: mouseIcon = XC_sb_h_double_arrow; break; + case RGFW_mouseResizeNS: mouseIcon = XC_sb_v_double_arrow; break; + case RGFW_mouseResizeNW: mouseIcon = XC_top_left_corner; break; + case RGFW_mouseResizeN: mouseIcon = XC_top_side; break; + case RGFW_mouseResizeNE: mouseIcon = XC_top_right_corner; break; + case RGFW_mouseResizeE: mouseIcon = XC_right_side; break; + case RGFW_mouseResizeSE: mouseIcon = XC_bottom_right_corner; break; + case RGFW_mouseResizeS: mouseIcon = XC_bottom_side; break; + case RGFW_mouseResizeSW: mouseIcon = XC_bottom_left_corner; break; + case RGFW_mouseResizeW: mouseIcon = XC_left_side; break; + case RGFW_mouseResizeAll: mouseIcon = XC_fleur; break; + case RGFW_mouseNotAllowed: mouseIcon = XC_pirate; break; + case RGFW_mousePointingHand: mouseIcon = XC_hand2; break; + default: return RGFW_FALSE; + } + + Cursor cursor = XCreateFontCursor(_RGFW->display, mouseIcon); + XDefineCursor(_RGFW->display, win->src.window, (Cursor) cursor); + XFreeCursor(_RGFW->display, (Cursor) cursor); + return RGFW_TRUE; +} + +void RGFW_FUNC(RGFW_window_hide)(RGFW_window* win) { + win->internal.flags |= (u32)RGFW_windowHide; + XUnmapWindow(_RGFW->display, win->src.window); + + XFlush(_RGFW->display); +} + +void RGFW_FUNC(RGFW_window_show) (RGFW_window* win) { + win->internal.flags &= ~(u32)RGFW_windowHide; + if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + + if (RGFW_window_isHidden(win) == RGFW_FALSE) { + return; + } + + XMapWindow(_RGFW->display, win->src.window); + RGFW_window_move(win, win->x, win->y); + + RGFW_waitForShowEvent_X11(win); + RGFW_window_setFullscreen(win, RGFW_window_isFullscreen(win)); + return; +} + +void RGFW_FUNC(RGFW_window_flash) (RGFW_window* win, RGFW_flashRequest request) { + if (RGFW_window_isInFocus(win) && request) { + return; + } + + XWMHints* wmhints = XGetWMHints(_RGFW->display, win->src.window); + if (wmhints == NULL) return; + + if (request) { + wmhints->flags |= XUrgencyHint; + if (request == RGFW_flashBriefly) + win->src.flashEnd = RGFW_linux_getTimeNS() + (u64)1e+9; + if (request == RGFW_flashUntilFocused) + win->src.flashEnd = (u64)-1; + } else { + win->src.flashEnd = 0; + wmhints->flags &= ~XUrgencyHint; + } + + XSetWMHints(_RGFW->display, win->src.window, wmhints); + XFree(wmhints); +} + +RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr)(char* str, size_t strCapacity) { + RGFW_init(); + RGFW_LOAD_ATOM(XSEL_DATA); RGFW_LOAD_ATOM(UTF8_STRING); RGFW_LOAD_ATOM(CLIPBOARD); + if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) == _RGFW->helperWindow) { + if (str != NULL) + RGFW_STRNCPY(str, _RGFW->clipboard, _RGFW->clipboard_len - 1); + _RGFW->clipboard[_RGFW->clipboard_len - 1] = '\0'; + return (RGFW_ssize_t)_RGFW->clipboard_len - 1; + } + + XEvent event; + int format; + unsigned long N, sizeN; + char* data; + Atom target; + + XConvertSelection(_RGFW->display, CLIPBOARD, UTF8_STRING, XSEL_DATA, _RGFW->helperWindow, CurrentTime); + XSync(_RGFW->display, 0); + while (1) { + XNextEvent(_RGFW->display, &event); + if (event.type != SelectionNotify) continue; + + if (event.xselection.selection != CLIPBOARD || event.xselection.property == 0) + return -1; + break; + } + + XGetWindowProperty(event.xselection.display, event.xselection.requestor, + event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target, + &format, &sizeN, &N, (u8**) &data); + + RGFW_ssize_t size; + if (sizeN > strCapacity && str != NULL) + size = -1; + + if ((target == UTF8_STRING || target == XA_STRING) && str != NULL) { + RGFW_MEMCPY(str, data, sizeN); + str[sizeN] = '\0'; + XFree(data); + } else if (str != NULL) size = -1; + + XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property); + size = (RGFW_ssize_t)sizeN; + + return size; +} + +i32 RGFW_XHandleClipboardSelectionHelper(void) { + RGFW_LOAD_ATOM(SAVE_TARGETS); + + XEvent event; + XPending(_RGFW->display); + + if (QLength(_RGFW->display) || XEventsQueued(_RGFW->display, QueuedAlready) + XEventsQueued(_RGFW->display, QueuedAfterReading)) + XNextEvent(_RGFW->display, &event); + else + return 0; + + switch (event.type) { + case SelectionRequest: + RGFW_XHandleClipboardSelection(&event); + return 0; + case SelectionNotify: + if (event.xselection.target == SAVE_TARGETS) + return 0; + break; + default: break; + } + + return 0; +} + +void RGFW_FUNC(RGFW_writeClipboard)(const char* text, u32 textLen) { + RGFW_LOAD_ATOM(SAVE_TARGETS); RGFW_LOAD_ATOM(CLIPBOARD); + RGFW_init(); + + /* request ownership of the clipboard section and request to convert it, this means its our job to convert it */ + XSetSelectionOwner(_RGFW->display, CLIPBOARD, _RGFW->helperWindow, CurrentTime); + if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) != _RGFW->helperWindow) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "X11 failed to become owner of clipboard selection"); + return; + } + + if (_RGFW->clipboard) + RGFW_FREE(_RGFW->clipboard); + + _RGFW->clipboard = (char*)RGFW_ALLOC(textLen); + RGFW_ASSERT(_RGFW->clipboard != NULL); + + RGFW_STRNCPY(_RGFW->clipboard, text, textLen - 1); + _RGFW->clipboard[textLen - 1] = '\0'; + _RGFW->clipboard_len = textLen; + return; +} + +RGFW_bool RGFW_FUNC(RGFW_window_isHidden)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + XWindowAttributes windowAttributes; + XGetWindowAttributes(_RGFW->display, win->src.window, &windowAttributes); + + return (windowAttributes.map_state != IsViewable); +} + +RGFW_bool RGFW_FUNC(RGFW_window_isMinimized)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(WM_STATE); + + Atom actual_type; + i32 actual_format; + unsigned long nitems, bytes_after; + unsigned char* prop_data; + + i32 status = XGetWindowProperty(_RGFW->display, win->src.window, WM_STATE, 0, 2, False, + AnyPropertyType, &actual_type, &actual_format, + &nitems, &bytes_after, &prop_data); + + if (status == Success && nitems >= 1 && prop_data == (unsigned char*)IconicState) { + XFree(prop_data); + return RGFW_TRUE; + } + + if (prop_data != NULL) + XFree(prop_data); + + XWindowAttributes windowAttributes; + XGetWindowAttributes(_RGFW->display, win->src.window, &windowAttributes); + return windowAttributes.map_state != IsViewable; +} + +RGFW_bool RGFW_FUNC(RGFW_window_isMaximized)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_LOAD_ATOM(_NET_WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); + RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); + + Atom actual_type; + i32 actual_format; + unsigned long nitems, bytes_after; + unsigned char* prop_data; + + i32 status = XGetWindowProperty(_RGFW->display, win->src.window, _NET_WM_STATE, 0, 1024, False, + XA_ATOM, &actual_type, &actual_format, + &nitems, &bytes_after, &prop_data); + + if (status != Success) { + if (prop_data != NULL) + XFree(prop_data); + + return RGFW_FALSE; + } + + u64 i; + for (i = 0; i < nitems; i++) { + if (prop_data[i] == _NET_WM_STATE_MAXIMIZED_VERT || + prop_data[i] == _NET_WM_STATE_MAXIMIZED_HORZ) { + XFree(prop_data); + return RGFW_TRUE; + } + } + + if (prop_data != NULL) + XFree(prop_data); + + return RGFW_FALSE; +} + +RGFWDEF void RGFW_XGetSystemContentDPI(float* dpi); +void RGFW_XGetSystemContentDPI(float* dpi) { + if (dpi == NULL) return; + float dpiOutput = 96.0f; + + #ifndef RGFW_NO_XRANDR + char* rms = XResourceManagerString(_RGFW->display); + if (rms == NULL) return; + + XrmDatabase db = XrmGetStringDatabase(rms); + if (db == NULL) return; + + XrmValue value; + char* type = NULL; + + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && type && RGFW_STRNCMP(type, "String", 7) == 0) + dpiOutput = (float)RGFW_ATOF(value.addr); + XrmDestroyDatabase(db); + #endif + + if (dpi) *dpi = dpiOutput; +} + +RGFWDEF XRRModeInfo* RGFW_XGetMode(XRRCrtcInfo* ci, XRRScreenResources* res, RRMode mode, RGFW_monitorMode* foundMode); +XRRModeInfo* RGFW_XGetMode(XRRCrtcInfo* ci, XRRScreenResources* res, RRMode mode, RGFW_monitorMode* foundMode) { + XRRModeInfo* mi = None; + for (i32 j = 0; j < res->nmode; j++) { + if (res->modes[j].id == mode) + mi = &res->modes[j]; + } + + if (mi == None) return NULL; + + if ((mi->modeFlags & RR_Interlace) != 0) return NULL; + + foundMode->w = (i32)mi->width; + foundMode->h = (i32)mi->height; + if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270) { + foundMode->w = (i32)mi->height; + foundMode->h = (i32)mi->width; + } else { + foundMode->w = (i32)mi->width; + foundMode->h = (i32)mi->height; + } + + RGFW_splitBPP((u32)DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)), foundMode); + + foundMode->src = (void*)mode; + + foundMode->refreshRate = 0; + if (mi->hTotal == 0 || mi->vTotal == 0) + return mi; + + u32 vTotal = mi->vTotal; + + if (mi->modeFlags & RR_DoubleScan) { + vTotal *= 2; + } + + if (mi->modeFlags & RR_Interlace) { + vTotal /= 2; + } + + i32 numerator = (i32)mi->dotClock; + i32 denominator = (i32)(mi->hTotal * vTotal); + float refreshRate = 0; + + if (denominator <= 0) { + denominator = 1; + } + + refreshRate = ((float)numerator / (float)denominator); + + foundMode->refreshRate = RGFW_ROUNDF((refreshRate * 100)) / 100.0f; + return mi; +} + +void RGFW_FUNC(RGFW_pollMonitors) (void) { + RGFW_init(); + + Window root = XDefaultRootWindow(_RGFW->display); + XRRScreenResources* res = XRRGetScreenResourcesCurrent(_RGFW->display, root); + if (res == 0) { + return; + } + + RROutput primary = XRRGetOutputPrimary(_RGFW->display, root); + + for (RGFW_monitorNode* node = _RGFW->monitors.list.head; node; node = node->next) { + node->disconnected = RGFW_TRUE; + } + + for (i32 i = 0; i < res->noutput; i++) { + RGFW_monitorNode* node = NULL; + for (node = _RGFW->monitors.list.head; node; node = node->next) { + if (node->rrOutput == res->outputs[i]) { + break; + } + } + + if (node) { + node->disconnected = RGFW_FALSE; + if (node->rrOutput == primary) { + _RGFW->monitors.primary = node; + } + continue; + } + + RGFW_monitor monitor; + + XRROutputInfo* info = XRRGetOutputInfo(_RGFW->display, res, res->outputs[i]); + if (info == NULL || info->connection != RR_Connected || info->crtc == None) { + continue; + } + + XRRCrtcInfo* ci = XRRGetCrtcInfo(_RGFW->display, res, info->crtc); + + if (ci == NULL) { + continue; + } + + float physW = (float)info->mm_width / 25.4f; + float physH = (float)info->mm_height / 25.4f; + + RGFW_STRNCPY(monitor.name, info->name, sizeof(monitor.name) - 1); + monitor.name[sizeof(monitor.name) - 1] = '\0'; + + if (physW > 0.0f && physH > 0.0f) { + monitor.physW = physW; + monitor.physH = physH; + } else { + monitor.physW = (float) ((float)ci->width / 96.f); + monitor.physH = (float) ((float)ci->height / 96.f); + } + + monitor.x = ci->x; + monitor.y = ci->y; + + float dpi = 96.0f; + RGFW_XGetSystemContentDPI(&dpi); + + monitor.scaleX = dpi / 96.0f; + monitor.scaleY = dpi / 96.0f; + + XRRModeInfo* mi = RGFW_XGetMode(ci, res, ci->mode, &monitor.mode); + + if (mi == NULL) { + break; + } + + XRRFreeCrtcInfo(ci); + + node = RGFW_monitors_add(&monitor); + if (node == NULL) break; + + node->rrOutput = res->outputs[i]; + node->crtc = info->crtc; + + if (node->rrOutput == primary) { + _RGFW->monitors.primary = node; + } + + XRRFreeOutputInfo(info); + info = NULL; + + RGFW_monitorCallback(_RGFW->root, &node->mon, RGFW_TRUE); + } + + XRRFreeScreenResources(res); + + RGFW_monitors_refresh(); +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_getWorkarea) (RGFW_monitor* monitor, i32* x, i32* y, i32* width, i32* height) { + RGFW_LOAD_ATOM(_NET_WORKAREA); + RGFW_LOAD_ATOM(_NET_CURRENT_DESKTOP); + + Window root = DefaultRootWindow(_RGFW->display); + + i32 areaX = monitor->x; + i32 areaY = monitor->y; + i32 areaW = monitor->mode.w; + i32 areaH = monitor->mode.h; + + if (_NET_WORKAREA && _NET_CURRENT_DESKTOP) { + Atom* extents = NULL; + Atom* desktop = NULL; + + Atom actualType = 0; + int actualFormat = 0; + unsigned long extentCount = 0, bytesAfter = 0; + XGetWindowProperty(_RGFW->display, root, _NET_WORKAREA, 0, LONG_MAX, False, XA_CARDINAL, &actualType, &actualFormat, &extentCount, &bytesAfter, (u8**) &extents); + + unsigned long count; + XGetWindowProperty(_RGFW->display, root, _NET_CURRENT_DESKTOP, 0, LONG_MAX, False, XA_CARDINAL, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &desktop); + + if (count) { + if (extentCount >= 4 && *desktop < extentCount / 4) { + i32 globalX = (i32)extents[*desktop * 4 + 0]; + i32 globalY = (i32)extents[*desktop * 4 + 1]; + i32 globalW = (i32)extents[*desktop * 4 + 2]; + i32 globalH = (i32)extents[*desktop * 4 + 3]; + + if (areaX < globalX) { + areaW -= globalX - areaX; + areaX = globalX; + } + + if (areaY < globalY) { + areaH -= globalY - areaY; + areaY = globalY; + } + + if (areaX + areaW > globalX + globalW) + areaW = globalX - areaX + globalW; + if (areaY + areaH > globalY + globalH) + areaH = globalY - areaY + globalH; + } + } + + if (extents) + XFree(extents); + if (desktop) + XFree(desktop); + } + + if (x) *x = areaX; + if (y) *y = areaY; + if (width) *width = areaW; + if (height) *height = areaH; + + return RGFW_TRUE; +} + +size_t RGFW_FUNC(RGFW_monitor_getModesPtr) (RGFW_monitor* monitor, RGFW_monitorMode** modes) { + size_t count = 0; + + XRRScreenResources* res = XRRGetScreenResourcesCurrent(_RGFW->display, DefaultRootWindow(_RGFW->display)); + if (res == NULL) return 0; + + XRRCrtcInfo* ci = XRRGetCrtcInfo(_RGFW->display, res, monitor->node->crtc); + XRROutputInfo* oi = XRRGetOutputInfo(_RGFW->display, res, monitor->node->rrOutput); + count = (size_t)oi->nmode; + + int i; + for (i = 0; modes && i < oi->nmode; i++) { + XRRModeInfo* mi = RGFW_XGetMode(ci, res, oi->modes[i], &((*modes)[i])); + RGFW_UNUSED(mi); + } + + XRRFreeOutputInfo(oi); + XRRFreeCrtcInfo(ci); + XRRFreeScreenResources(res); + + return count; +} + +size_t RGFW_FUNC(RGFW_monitor_getGammaRampPtr) (RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + RGFW_UNUSED(monitor); RGFW_UNUSED(ramp); +#ifndef RGFW_NO_XRANDR + size_t size = (size_t)XRRGetCrtcGammaSize(_RGFW->display, monitor->node->crtc); + XRRCrtcGamma* gamma = XRRGetCrtcGamma(_RGFW->display, monitor->node->crtc); + + if (ramp) { + RGFW_MEMCPY(ramp->red, gamma->red, size * sizeof(unsigned short)); + RGFW_MEMCPY(ramp->green, gamma->green, size * sizeof(unsigned short)); + RGFW_MEMCPY(ramp->blue, gamma->blue, size * sizeof(unsigned short)); + } + + XRRFreeGamma(gamma); + return size; +#endif + + return 0; +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_setGammaRamp) (RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + RGFW_UNUSED(monitor); RGFW_UNUSED(ramp); + +#ifndef RGFW_NO_XRANDR + size_t size = (size_t)XRRGetCrtcGammaSize(_RGFW->display, monitor->node->crtc); + if (size != ramp->count) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errX11, "X11: Gamma ramp size must match current ramp size"); + return RGFW_FALSE; + } + + XRRCrtcGamma* gamma = XRRAllocGamma((int)ramp->count); + + memcpy(gamma->red, ramp->red, ramp->count * sizeof(unsigned short)); + memcpy(gamma->green, ramp->green, ramp->count * sizeof(unsigned short)); + memcpy(gamma->blue, ramp->blue, ramp->count * sizeof(unsigned short)); + + XRRSetCrtcGamma(_RGFW->display, monitor->node->crtc, gamma); + XRRFreeGamma(gamma); + + return RGFW_TRUE; +#endif + return RGFW_FALSE; +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_setMode)(RGFW_monitor* mon, RGFW_monitorMode* mode) { + RGFW_bool out = RGFW_FALSE; + + XRRScreenResources* res = XRRGetScreenResourcesCurrent(_RGFW->display, DefaultRootWindow(_RGFW->display)); + XRRCrtcInfo* ci = XRRGetCrtcInfo(_RGFW->display, res, mon->node->crtc); + + if (XRRSetCrtcConfig(_RGFW->display, res, mon->node->crtc, CurrentTime, ci->x, ci->y, (RRMode)mode->src, ci->rotation, ci->outputs, ci->noutput) == True) { + out = RGFW_TRUE; + } + + XRRFreeCrtcInfo(ci); + XRRFreeScreenResources(res); + return out; +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_requestMode)(RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request) { + #ifndef RGFW_NO_XRANDR + RGFW_init(); + + RGFW_bool output = RGFW_FALSE; + + XRRScreenResources* res = XRRGetScreenResourcesCurrent(_RGFW->display, DefaultRootWindow(_RGFW->display)); + if (res == NULL) return RGFW_FALSE; + + XRRCrtcInfo* ci = XRRGetCrtcInfo(_RGFW->display, res, mon->node->crtc); + XRROutputInfo* oi = XRRGetOutputInfo(_RGFW->display, res, mon->node->rrOutput); + + RRMode native = None; + + int i; + for (i = 0; i < oi->nmode; i++) { + RGFW_monitorMode foundMode; + XRRModeInfo* mi = RGFW_XGetMode(ci, res, oi->modes[i], &foundMode); + if (mi == NULL) { + continue; + } + + if (RGFW_monitorModeCompare(mode, &foundMode, request)) { + native = mi->id; + output = RGFW_TRUE; + mon->mode = foundMode; + break; + } + } + + if (native) { + XRRSetCrtcConfig(_RGFW->display, res, mon->node->crtc, CurrentTime, ci->x, ci->y, native, ci->rotation, ci->outputs, ci->noutput); + } + + XRRFreeOutputInfo(oi); + XRRFreeCrtcInfo(ci); + XRRFreeScreenResources(res); + return output; +#endif + return RGFW_FALSE; +} + +RGFW_monitor* RGFW_FUNC(RGFW_window_getMonitor) (RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + XWindowAttributes attrs; + if (!XGetWindowAttributes(_RGFW->display, win->src.window, &attrs)) { + return NULL; + } + + for (RGFW_monitorNode* node = _RGFW->monitors.list.head; node; node = node->next) { + if ((attrs.x < node->mon.x + node->mon.mode.w) && (attrs.x + attrs.width > node->mon.x) && (attrs.y < node->mon.y + node->mon.mode.h) && (attrs.y + attrs.height > node->mon.y)) + return &node->mon; + } + + + return &_RGFW->monitors.list.head->mon; +} + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_FUNC(RGFW_window_createContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* context, RGFW_glHints* hints) { + /* for checking extensions later */ + const char sRGBARBstr[] = "GLX_ARB_framebuffer_sRGB"; + const char sRGBEXTstr[] = "GLX_EXT_framebuffer_sRGB"; + const char noErorrStr[] = "GLX_ARB_create_context_no_error"; + const char flushStr[] = "GLX_ARB_context_flush_control"; + const char robustStr[] = "GLX_ARB_create_context_robustness"; + + /* basic RGFW int */ + win->src.ctx.native = context; + win->src.gfxType = RGFW_gfxNativeOpenGL; + + /* This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used */ + RGFW_bool showWindow = RGFW_FALSE; + if (win->src.window) { + showWindow = (RGFW_window_isMinimized(win) == RGFW_FALSE); + RGFW_window_closePlatform(win); + } + + RGFW_bool transparent = (win->internal.flags & RGFW_windowTransparent); + + /* start by creating a GLX config / X11 Viusal */ + XVisualInfo visual; + GLXFBConfig bestFbc; + + i32 visual_attribs[40]; + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, visual_attribs, 40); + RGFW_attribStack_pushAttribs(&stack, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR); + RGFW_attribStack_pushAttribs(&stack, GLX_X_RENDERABLE, 1); + RGFW_attribStack_pushAttribs(&stack, GLX_RENDER_TYPE, GLX_RGBA_BIT); + RGFW_attribStack_pushAttribs(&stack, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT); + RGFW_attribStack_pushAttribs(&stack, GLX_DOUBLEBUFFER, 1); + RGFW_attribStack_pushAttribs(&stack, GLX_ALPHA_SIZE, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, GLX_DEPTH_SIZE, hints->depth); + RGFW_attribStack_pushAttribs(&stack, GLX_STENCIL_SIZE, hints->stencil); + RGFW_attribStack_pushAttribs(&stack, GLX_STEREO, hints->stereo); + RGFW_attribStack_pushAttribs(&stack, GLX_AUX_BUFFERS, hints->auxBuffers); + RGFW_attribStack_pushAttribs(&stack, GLX_RED_SIZE, hints->red); + RGFW_attribStack_pushAttribs(&stack, GLX_GREEN_SIZE, hints->green); + RGFW_attribStack_pushAttribs(&stack, GLX_BLUE_SIZE, hints->blue); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_RED_SIZE, hints->accumRed); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_GREEN_SIZE, hints->accumGreen); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_BLUE_SIZE, hints->accumBlue); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_ALPHA_SIZE, hints->accumAlpha); + + if (hints->sRGB) { + if (RGFW_extensionSupportedPlatform_OpenGL(sRGBARBstr, sizeof(sRGBARBstr))) + RGFW_attribStack_pushAttribs(&stack, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, hints->sRGB); + if (RGFW_extensionSupportedPlatform_OpenGL(sRGBEXTstr, sizeof(sRGBEXTstr))) + RGFW_attribStack_pushAttribs(&stack, GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, hints->sRGB); + } + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + /* find the configs */ + i32 fbcount; + GLXFBConfig* fbc = glXChooseFBConfig(_RGFW->display, DefaultScreen(_RGFW->display), visual_attribs, &fbcount); + + i32 best_fbc = -1; + i32 best_depth = 0; + i32 best_samples = 0; + + if (fbcount == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to find any valid GLX visual configs."); + return 0; + } + + /* search through all found configs to find the best match */ + i32 i; + for (i = 0; i < fbcount; i++) { + XVisualInfo* vi = glXGetVisualFromFBConfig(_RGFW->display, fbc[i]); + if (vi == NULL) + continue; + + i32 samp_buf, samples; + glXGetFBConfigAttrib(_RGFW->display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); + glXGetFBConfigAttrib(_RGFW->display, fbc[i], GLX_SAMPLES, &samples); + + if (best_fbc == -1) best_fbc = i; + if ((!(transparent) || vi->depth == 32) && best_depth == 0) { + best_fbc = i; + best_depth = vi->depth; + } + if ((!(transparent) || vi->depth == 32) && samples <= hints->samples && samples > best_samples) { + best_fbc = i; + best_depth = vi->depth; + best_samples = samples; + } + XFree(vi); + } + + if (best_fbc == -1) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to get a valid GLX visual."); + return 0; + } + + /* we found a config */ + bestFbc = fbc[best_fbc]; + XVisualInfo* vi = glXGetVisualFromFBConfig(_RGFW->display, bestFbc); + if (vi->depth != 32 && transparent) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to to find a matching visual with a 32-bit depth."); + + if (best_samples < hints->samples) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to load a matching sample count."); + + XFree(fbc); + visual = *vi; + XFree(vi); + + /* use the visual to create a new window */ + RGFW_XCreateWindow(visual, "", win->internal.flags, win); + + if (showWindow) { + RGFW_window_show(win); + } + + /* create the actual OpenGL context */ + i32 context_attribs[40]; + RGFW_attribStack_init(&stack, context_attribs, 40); + + i32 mask = 0; + switch (hints->profile) { + case RGFW_glES: mask |= GLX_CONTEXT_ES_PROFILE_BIT_EXT; break; + case RGFW_glForwardCompatibility: mask |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; break; + case RGFW_glCompatibility: mask |= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; break; + case RGFW_glCore: mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; break; + default: mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; break; + } + + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_PROFILE_MASK_ARB, mask); + + if (hints->minor || hints->major) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_MAJOR_VERSION_ARB, hints->major); + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_MINOR_VERSION_ARB, hints->minor); + } + + + if (RGFW_extensionSupportedPlatform_OpenGL(flushStr, sizeof(flushStr))) { + if (hints->releaseBehavior == RGFW_glReleaseFlush) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB); + } else if (hints->releaseBehavior == RGFW_glReleaseNone) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB); + } + } + + i32 flags = 0; + if (hints->debug) flags |= GLX_CONTEXT_DEBUG_BIT_ARB; + if (hints->robustness && RGFW_extensionSupportedPlatform_OpenGL(robustStr, sizeof(robustStr))) flags |= GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB; + if (flags) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_FLAGS_ARB, flags); + } + + if (RGFW_extensionSupportedPlatform_OpenGL(noErorrStr, sizeof(noErorrStr))) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_OPENGL_NO_ERROR_ARB, hints->noError); + } + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + /* create the context */ + glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; + char str[] = "glXCreateContextAttribsARB"; + glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB((u8*) str); + + GLXContext ctx = NULL; + if (hints->share) { + ctx = hints->share->ctx; + } + + if (glXCreateContextAttribsARB == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load proc address 'glXCreateContextAttribsARB', loading a generic OpenGL context."); + win->src.ctx.native->ctx = glXCreateContext(_RGFW->display, &visual, ctx, True); + } else { + _RGFW->x11Error = NULL; + win->src.ctx.native->ctx = glXCreateContextAttribsARB(_RGFW->display, bestFbc, ctx, True, context_attribs); + if (_RGFW->x11Error || win->src.ctx.native->ctx == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create an OpenGL context with AttribsARB, loading a generic OpenGL context."); + win->src.ctx.native->ctx = glXCreateContext(_RGFW->display, &visual, ctx, True); + } + } + + #ifndef RGFW_NO_GLXWINDOW + win->src.ctx.native->window = glXCreateWindow(_RGFW->display, bestFbc, win->src.window, NULL); + #else + win->src.ctx.native->window = win->src.window; + #endif + + glXMakeCurrent(_RGFW->display, (Drawable)win->src.ctx.native->window, (GLXContext)win->src.ctx.native->ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + + RGFW_window_swapInterval_OpenGL(win, 0); + + return RGFW_TRUE; +} + +void RGFW_FUNC(RGFW_window_deleteContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* ctx) { + #ifndef RGFW_NO_GLXWINDOW + if (win->src.ctx.native->window != win->src.window) { + glXDestroyWindow(_RGFW->display, win->src.ctx.native->window); + } + #endif + + glXDestroyContext(_RGFW->display, ctx->ctx); + win->src.ctx.native = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +RGFW_bool RGFW_FUNC(RGFW_extensionSupportedPlatform_OpenGL)(const char * extension, size_t len) { + RGFW_init(); + const char* extensions = glXQueryExtensionsString(_RGFW->display, XDefaultScreen(_RGFW->display)); + return (extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len); +} + +RGFW_proc RGFW_FUNC(RGFW_getProcAddress_OpenGL)(const char* procname) { return glXGetProcAddress((u8*) procname); } + +void RGFW_FUNC(RGFW_window_makeCurrentContext_OpenGL) (RGFW_window* win) { if (win) RGFW_ASSERT(win->src.ctx.native); + if (win == NULL) + glXMakeCurrent(NULL, (Drawable)NULL, (GLXContext) NULL); + else + glXMakeCurrent(_RGFW->display, (Drawable)win->src.ctx.native->window, (GLXContext) win->src.ctx.native->ctx); + return; +} +void* RGFW_FUNC(RGFW_getCurrentContext_OpenGL) (void) { return glXGetCurrentContext(); } +void RGFW_FUNC(RGFW_window_swapBuffers_OpenGL) (RGFW_window* win) { RGFW_ASSERT(win->src.ctx.native); glXSwapBuffers(_RGFW->display, win->src.ctx.native->window); } + +void RGFW_FUNC(RGFW_window_swapInterval_OpenGL) (RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); + /* cached pfn to avoid calling glXGetProcAddress more than once */ + static PFNGLXSWAPINTERVALEXTPROC pfn = NULL; + static int (*pfn2)(int) = NULL; + + if (pfn == NULL) { + u8 str[] = "glXSwapIntervalEXT"; + pfn = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress(str); + if (pfn == NULL) { + pfn = (PFNGLXSWAPINTERVALEXTPROC)1; + const char* array[] = {"GLX_MESA_swap_control", "GLX_SGI_swap_control"}; + + size_t i; + for (i = 0; i < sizeof(array) / sizeof(char*) && pfn2 == NULL; i++) { + pfn2 = (int(*)(int))glXGetProcAddress((u8*)array[i]); + } + + if (pfn2 != NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function, fallingback to the native swapinterval function"); + } else { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function"); + } + } + } + + if (pfn != (PFNGLXSWAPINTERVALEXTPROC)1) { + pfn(_RGFW->display, win->src.ctx.native->window, swapInterval); + } + else if (pfn2 != NULL) { + pfn2(swapInterval); + } +} +#endif /* RGFW_OPENGL */ + +i32 RGFW_initPlatform_X11(void) { + #ifdef RGFW_USE_XDL + XDL_init(); + #endif + + #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor-1.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so"); + #else + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so.1"); + #endif + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageCreate); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageDestroy); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageLoadCursor); + #endif + + #if !defined(RGFW_NO_X11_XI_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so"); + #else + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so.6"); + #endif + RGFW_PROC_DEF(X11Xihandle, XISelectEvents); + #endif + + #if !defined(RGFW_NO_X11_EXT_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so"); + #else + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so.6"); + #endif + RGFW_PROC_DEF(X11XEXThandle, XSyncCreateCounter); + RGFW_PROC_DEF(X11XEXThandle, XSyncIntToValue); + RGFW_PROC_DEF(X11XEXThandle, XSyncSetCounter); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineRegion); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineMask); + #endif + + XInitThreads(); /*!< init X11 threading */ + _RGFW->display = XOpenDisplay(0); + _RGFW->context = XUniqueContext(); + + XSetWindowAttributes wa; + RGFW_MEMSET(&wa, 0, sizeof(wa)); + wa.event_mask = PropertyChangeMask; + _RGFW->helperWindow = XCreateWindow(_RGFW->display, XDefaultRootWindow(_RGFW->display), 0, 0, 1, 1, 0, 0, + InputOnly, DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)), CWEventMask, &wa); + + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); + _RGFW->clipboard = NULL; + + XkbComponentNamesRec rec; + XkbDescPtr desc = XkbGetMap(_RGFW->display, 0, XkbUseCoreKbd); + XkbDescPtr evdesc; + XSetErrorHandler(RGFW_XErrorHandler); + u8 old[256]; + + XkbGetNames(_RGFW->display, XkbKeyNamesMask, desc); + + RGFW_MEMSET(&rec, 0, sizeof(rec)); + char evdev[] = "evdev"; + rec.keycodes = evdev; + evdesc = XkbGetKeyboardByName(_RGFW->display, XkbUseCoreKbd, &rec, XkbGBN_KeyNamesMask, XkbGBN_KeyNamesMask, False); + /* memo: RGFW_keycodes[x11 keycode] = rgfw keycode */ + if(evdesc != NULL && desc != NULL) { + int i, j; + for(i = 0; i < (int)sizeof(old); i++){ + old[i] = _RGFW->keycodes[i]; + _RGFW->keycodes[i] = 0; + } + for(i = evdesc->min_key_code; i <= evdesc->max_key_code; i++){ + for(j = desc->min_key_code; j <= desc->max_key_code; j++){ + if(RGFW_STRNCMP(evdesc->names->keys[i].name, desc->names->keys[j].name, XkbKeyNameLength) == 0){ + _RGFW->keycodes[j] = old[i]; + break; + } + } + } + XkbFreeKeyboard(desc, 0, True); + XkbFreeKeyboard(evdesc, 0, True); + } + + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; + XISetMask(mask, XI_RawMotion); + + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + + XISelectEvents(_RGFW->display, XDefaultRootWindow(_RGFW->display), &em, 1); + +#ifndef RGFW_NO_XRANDR + i32 errorBase; + if (XRRQueryExtension(_RGFW->display, &_RGFW->xrandrEventBase, &errorBase)) { + XRRSelectInput(_RGFW->display, RootWindow(_RGFW->display, DefaultScreen(_RGFW->display)), RROutputChangeNotifyMask); + } +#endif + + + return 0; +} + +void RGFW_deinitPlatform_X11(void) { + #define RGFW_FREE_LIBRARY(x) if (x != NULL) dlclose(x); x = NULL; + /* to save the clipboard on the x server after the window is closed */ + RGFW_LOAD_ATOM(CLIPBOARD_MANAGER); RGFW_LOAD_ATOM(CLIPBOARD); + RGFW_LOAD_ATOM(SAVE_TARGETS); + if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) == _RGFW->helperWindow) { + XConvertSelection(_RGFW->display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, _RGFW->helperWindow, CurrentTime); + while (RGFW_XHandleClipboardSelectionHelper()); + } + if (_RGFW->clipboard) { + RGFW_FREE(_RGFW->clipboard); + _RGFW->clipboard = NULL; + } + + if (_RGFW->hiddenMouse) { + RGFW_freeMouse(_RGFW->hiddenMouse); + _RGFW->hiddenMouse = NULL; + } + + XDestroyWindow(_RGFW->display, (Drawable) _RGFW->helperWindow); /*!< close the window */ + XCloseDisplay(_RGFW->display); /*!< kill connection to the x server */ + + #if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) + RGFW_FREE_LIBRARY(X11Cursorhandle); + #endif + #if !defined(RGFW_NO_X11_XI_PRELOAD) + RGFW_FREE_LIBRARY(X11Xihandle); + #endif + + #ifdef RGFW_USE_XDL + XDL_close(); + #endif + + #if !defined(RGFW_NO_X11_EXT_PRELOAD) + RGFW_FREE_LIBRARY(X11XEXThandle); + #endif +} + +void RGFW_FUNC(RGFW_window_closePlatform)(RGFW_window* win) { + XFreeGC(_RGFW->display, win->src.gc); + XDeleteContext(_RGFW->display, win->src.window, _RGFW->context); + XDestroyWindow(_RGFW->display, (Drawable) win->src.window); /*!< close the window */ + return; +} + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUSurfaceSourceXlibWindow fromXlib = {0}; + fromXlib.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + fromXlib.display = _RGFW->display; + fromXlib.window = window->src.window; + + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromXlib.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + +#endif +/* + End of X11 linux / wayland / unix defines +*/ + +/* + + Start of Wayland defayland +*/ + +#ifdef RGFW_WAYLAND +#ifdef RGFW_X11 +#undef RGFW_FUNC /* remove previous define */ +#define RGFW_FUNC(func) func##_Wayland +#else +#define RGFW_FUNC(func) func +#endif + +/* +Wayland TODO: (out of date) +- fix RGFW_keyPressed lock state + + RGFW_windowMoved, the window was moved (by the user) + RGFW_windowRefresh The window content needs to be refreshed + + RGFW_dataDrop a file has been dropped into the window + RGFW_dataDrag + +- window args: + #define RGFW_windowNoResize the window cannot be resized by the user + #define RGFW_windowAllowDND the window supports drag and drop + #define RGFW_scaleToMonitor scale the window to the screen + +- other missing functions functions ("TODO wayland") (~30 functions) +- fix buffer rendering weird behavior +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct wl_display* RGFW_getDisplay_Wayland(void) { return _RGFW->wl_display; } +struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win) { return win->src.surface; } + + +/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ +#include "xdg-shell.h" +#include "xdg-toplevel-icon-v1.h" +#include "xdg-decoration-unstable-v1.h" +#include "relative-pointer-unstable-v1.h" +#include "pointer-constraints-unstable-v1.h" +#include "xdg-output-unstable-v1.h" + + +void RGFW_toggleWaylandMaximized(RGFW_window* win, RGFW_bool maximized); + +static void RGFW_wl_setOpaque(RGFW_window* win) { + struct wl_region* wl_region = wl_compositor_create_region(_RGFW->compositor); + + if (!wl_region) return; /* return if no region was created */ + + wl_region_add(wl_region, 0, 0, win->w, win->h); + wl_surface_set_opaque_region(win->src.surface, wl_region); + wl_region_destroy(wl_region); + +} + +static void RGFW_wl_xdg_wm_base_ping_handler(void* data, struct xdg_wm_base* wm_base, + u32 serial) { + RGFW_UNUSED(data); + xdg_wm_base_pong(wm_base, serial); +} +static void RGFW_wl_xdg_surface_configure_handler(void* data, struct xdg_surface* xdg_surface, + u32 serial) { + + xdg_surface_ack_configure(xdg_surface, serial); + + RGFW_window* win = (RGFW_window*)data; + + if (win == NULL) { + win = _RGFW->kbOwner; + if (win == NULL) + return; + } + + /* useful for libdecor */ + if (win->src.activated != win->src.pending_activated) { + win->src.activated = win->src.pending_activated; + } + + if (win->src.maximized != win->src.pending_maximized) { + RGFW_toggleWaylandMaximized(win, win->src.pending_maximized); + + RGFW_window_checkMode(win); + } + + + if (win->src.resizing) { + + /* Do not create a resize event if the window is maximized */ + if (!win->src.maximized) { + RGFW_windowResizedCallback(win, win->w, win->h); + } + RGFW_window_resize(win, win->w, win->h); + if (!(win->internal.flags & RGFW_windowTransparent)) { + RGFW_wl_setOpaque(win); + } + } + +} + +static void RGFW_wl_xdg_toplevel_configure_handler(void* data, struct xdg_toplevel* toplevel, + i32 width, i32 height, struct wl_array* states) { + + RGFW_UNUSED(toplevel); + RGFW_window* win = (RGFW_window*)data; + + + win->src.pending_activated = RGFW_FALSE; + win->src.pending_maximized = RGFW_FALSE; + win->src.resizing = RGFW_FALSE; + + + enum xdg_toplevel_state* state; + wl_array_for_each(state, states) { + switch (*state) { + case XDG_TOPLEVEL_STATE_ACTIVATED: + win->src.pending_activated = RGFW_TRUE; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + win->src.pending_maximized = RGFW_TRUE; + break; + default: + break; + } + + } + /* if width and height are not zero and are not the same as the window */ + /* the window is resizing so update the values */ + if ((width && height) && (win->w != width || win->h != height)) { + win->src.resizing = RGFW_TRUE; + win->src.w = win->w = width; + win->src.h = win->h = height; + } +} + +static void RGFW_wl_xdg_toplevel_close_handler(void* data, struct xdg_toplevel *toplevel) { + RGFW_UNUSED(toplevel); + RGFW_window* win = (RGFW_window*)data; + + if (!win->internal.shouldClose) { + RGFW_windowQuitCallback(win); + } +} + +static void RGFW_wl_xdg_decoration_configure_handler(void* data, + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, u32 mode) { + RGFW_window* win = (RGFW_window*)data; RGFW_UNUSED(zxdg_toplevel_decoration_v1); + + /* this is expected to run once */ + /* set the decoration mode set by earlier request */ + if (mode != win->src.decoration_mode) { + win->src.decoration_mode = mode; + } +} + +static void RGFW_wl_shm_format_handler(void* data, struct wl_shm *shm, u32 format) { + RGFW_UNUSED(data); RGFW_UNUSED(shm); RGFW_UNUSED(format); +} + +static void RGFW_wl_relative_pointer_motion(void *data, struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, + u32 time_hi, u32 time_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) { + + RGFW_UNUSED(zwp_relative_pointer_v1); RGFW_UNUSED(time_hi); RGFW_UNUSED(time_lo); + RGFW_UNUSED(dx_unaccel); RGFW_UNUSED(dy_unaccel); + + RGFW_info* RGFW = (RGFW_info*)data; + + RGFW_ASSERT(RGFW->mouseOwner != NULL); + RGFW_window* win = RGFW->mouseOwner; + + RGFW_ASSERT(win); + + float vecX = (float)wl_fixed_to_double(dx); + float vecY = (float)wl_fixed_to_double(dy); + RGFW_mousePosCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, vecX, vecY); +} + +static void RGFW_wl_pointer_locked(void *data, struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) { + RGFW_UNUSED(zwp_locked_pointer_v1); + RGFW_info* RGFW = (RGFW_info*)data; + wl_pointer_set_cursor(RGFW->wl_pointer, RGFW->mouse_enter_serial, NULL, 0, 0); /* draw no cursor */ +} + +static void RGFW_wl_pointer_enter(void* data, struct wl_pointer* pointer, u32 serial, + struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + + /* save when the pointer is locked or using default cursor */ + RGFW->mouse_enter_serial = serial; + win->internal.mouseInside = RGFW_TRUE; + RGFW->windowState.mouseEnter = RGFW_TRUE; + + RGFW->mouseOwner = win; + + /* set the cursor */ + if (win->src.using_custom_cursor) { + wl_pointer_set_cursor(pointer, serial, win->src.custom_cursor_surface, 0, 0); + } + else { + RGFW_window_setMouseDefault(win); + } + + i32 x = (i32)wl_fixed_to_double(surface_x); + i32 y = (i32)wl_fixed_to_double(surface_y); + RGFW_mouseNotifyCallback(win, x, y, RGFW_TRUE); +} + +static void RGFW_wl_pointer_leave(void* data, struct wl_pointer *pointer, u32 serial, struct wl_surface *surface) { + RGFW_UNUSED(pointer); RGFW_UNUSED(serial); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW_info* RGFW = (RGFW_info*)data; + if (RGFW->mouseOwner == win) + RGFW->mouseOwner = NULL; + + RGFW_mouseNotifyCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, RGFW_FALSE); +} + +static void RGFW_wl_pointer_motion(void* data, struct wl_pointer *pointer, u32 time, wl_fixed_t x, wl_fixed_t y) { + RGFW_UNUSED(pointer); RGFW_UNUSED(time); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_ASSERT(RGFW->mouseOwner != NULL); + + RGFW_window* win = RGFW->mouseOwner; + + i32 convertedX = (i32)wl_fixed_to_double(x); + i32 convertedY = (i32)wl_fixed_to_double(y); + float newVecX = (float)(convertedX - win->internal.lastMouseX); + float newVecY = (float)(convertedY - win->internal.lastMouseY); + + RGFW_mousePosCallback(win, convertedX, convertedY, newVecX, newVecY); +} + +static void RGFW_wl_pointer_button(void* data, struct wl_pointer *pointer, u32 serial, u32 time, u32 button, u32 state) { + RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); + RGFW_info* RGFW = (RGFW_info*)data; + + RGFW_ASSERT(RGFW->mouseOwner != NULL); + RGFW_window* win = RGFW->mouseOwner; + + u32 b = (button - 0x110); + + /* flip right and middle button codes */ + if (b == 1) b = 2; + else if (b == 2) b = 1; + + RGFW_mouseButtonCallback(win, (u8)b, RGFW_BOOL(state)); +} + +static void RGFW_wl_pointer_axis(void* data, struct wl_pointer *pointer, u32 time, u32 axis, wl_fixed_t value) { + RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_ASSERT(RGFW->mouseOwner != NULL); + RGFW_window* win = RGFW->mouseOwner; + + float scrollX = 0.0; + float scrollY = 0.0; + + if (!(win->internal.enabledEvents & (RGFW_BIT(RGFW_mouseScroll)))) return; + + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) + scrollX = (float)(-wl_fixed_to_double(value) / 10.0); + else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + scrollY = (float)(-wl_fixed_to_double(value) / 10.0); + + RGFW_mouseScrollCallback(win, scrollX, scrollY); +} + + +static void RGFW_doNothing(void) { } + +static void RGFW_wl_keyboard_keymap(void* data, struct wl_keyboard *keyboard, u32 format, i32 fd, u32 size) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(format); + RGFW_info* RGFW = (RGFW_info*)data; + + char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); + xkb_keymap_unref(RGFW->keymap); + RGFW->keymap = xkb_keymap_new_from_string(RGFW->xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap(keymap_string, size); + close(fd); + xkb_state_unref(RGFW->xkb_state); + RGFW->xkb_state = xkb_state_new(RGFW->keymap); +} + +static void RGFW_wl_keyboard_enter(void* data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface, struct wl_array *keys) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(keys); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW->kbOwner = win; + + + // this is to prevent race conditions + if (RGFW->data_device != NULL && win->src.data_source != NULL) { + wl_data_device_set_selection(RGFW->data_device, win->src.data_source, serial); + } + /* is set when RGFW_window_minimize is called; if the minimize button is */ + /* pressed this flag is not set since there is no event to listen for */ + if (win->src.minimized == RGFW_TRUE) win->src.minimized = RGFW_FALSE; + + RGFW_focusCallback(win, RGFW_TRUE); +} + +static void RGFW_wl_keyboard_leave(void* data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW->kbOwner == win) + RGFW->kbOwner = NULL; + + RGFW_focusCallback(win, RGFW_FALSE); +} + +static void RGFW_wl_keyboard_key(void* data, struct wl_keyboard *keyboard, u32 serial, u32 time, u32 key, u32 state) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + + RGFW_info* RGFW = (RGFW_info*)data; + if (RGFW->kbOwner == NULL) return; + + RGFW_window *RGFW_key_win = RGFW->kbOwner; + xkb_keysym_t keysym = xkb_state_key_get_one_sym(RGFW->xkb_state, key + 8); + + u32 RGFWkey = RGFW_apiKeyToRGFW(key + 8); + + RGFW_updateKeyMods(RGFW_key_win, RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "Lock")), RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "Mod2")), RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "ScrollLock"))); + RGFW_keyCallback(RGFW_key_win, (u8)RGFWkey, (u8)keysym, RGFW_key_win->internal.mod, RGFW_window_isKeyDown(RGFW_key_win, (u8)RGFWkey), RGFW_BOOL(state)); +} + +static void RGFW_wl_keyboard_modifiers(void* data, struct wl_keyboard *keyboard, u32 serial, u32 mods_depressed, u32 mods_latched, u32 mods_locked, u32 group) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + RGFW_info* RGFW = (RGFW_info*)data; + xkb_state_update_mask(RGFW->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} + +static void RGFW_wl_seat_capabilities(void* data, struct wl_seat *seat, u32 capabilities) { + RGFW_info* RGFW = (RGFW_info*)data; + static struct wl_pointer_listener pointer_listener; + RGFW_MEMSET(&pointer_listener, 0, sizeof(pointer_listener)); + pointer_listener.enter = &RGFW_wl_pointer_enter; + pointer_listener.leave = &RGFW_wl_pointer_leave; + pointer_listener.motion = &RGFW_wl_pointer_motion; + pointer_listener.button = &RGFW_wl_pointer_button; + pointer_listener.axis = &RGFW_wl_pointer_axis; + + static struct wl_keyboard_listener keyboard_listener; + RGFW_MEMSET(&keyboard_listener, 0, sizeof(keyboard_listener)); + keyboard_listener.keymap = &RGFW_wl_keyboard_keymap; + keyboard_listener.enter = &RGFW_wl_keyboard_enter; + keyboard_listener.leave = &RGFW_wl_keyboard_leave; + keyboard_listener.key = &RGFW_wl_keyboard_key; + keyboard_listener.modifiers = &RGFW_wl_keyboard_modifiers; + + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !RGFW->wl_pointer) { + RGFW->wl_pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(RGFW->wl_pointer, &pointer_listener, RGFW); + } + if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !RGFW->wl_keyboard) { + RGFW->wl_keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(RGFW->wl_keyboard, &keyboard_listener, RGFW); + } + + if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && RGFW->wl_pointer) { + wl_pointer_destroy(RGFW->wl_pointer); + } + if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && RGFW->wl_keyboard) { + wl_keyboard_destroy(RGFW->wl_keyboard); + } +} + +static void RGFW_wl_output_set_geometry(void *data, struct wl_output *wl_output, + i32 x, i32 y, i32 physical_width, i32 physical_height, + i32 subpixel, const char *make, const char *model, i32 transform) { + + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + monitor->x = x; + monitor->y = y; + + monitor->physW = (float)physical_width / 25.4f; + monitor->physH = (float)physical_height / 25.4f; + + RGFW_UNUSED(wl_output); + RGFW_UNUSED(subpixel); + RGFW_UNUSED(make); + RGFW_UNUSED(model); + RGFW_UNUSED(transform); +} + +static void RGFW_wl_output_handle_mode(void *data, struct wl_output *wl_output, u32 flags, + i32 width, i32 height, i32 refresh) { + + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + RGFW_monitorMode mode; + mode.w = width; + mode.h = height; + mode.refreshRate = (float)refresh / 1000.0f; + mode.src = wl_output; + + monitor->node->modeCount += 1; + + RGFW_monitorMode* modes = (RGFW_monitorMode*)RGFW_ALLOC(monitor->node->modeCount * sizeof(RGFW_monitorMode)); + + if (monitor->node->modeCount > 1) { + RGFW_monitor_getModesPtr(monitor, &modes); + RGFW_FREE(monitor->node->modes); + } + + modes[monitor->node->modeCount - 1] = mode; + monitor->node->modes = modes; + + if (flags & WL_OUTPUT_MODE_CURRENT) { + monitor->mode = mode; + } else { + } +} + +static void RGFW_wl_output_set_scale(void *data, struct wl_output *wl_output, i32 factor) { + RGFW_UNUSED(wl_output); + RGFW_monitor* mon = &((RGFW_monitorNode*)data)->mon; + + mon->scaleX = (float)factor; + mon->scaleY = (float)factor; +} + +static void RGFW_wl_output_set_name(void *data, struct wl_output *wl_output, const char *name) { + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + RGFW_STRNCPY(monitor->name, name, sizeof(monitor->name) - 1); + monitor->name[sizeof(monitor->name) - 1] = '\0'; + + RGFW_UNUSED(wl_output); + +} + +static void RGFW_xdg_output_logical_pos(void *data, struct zxdg_output_v1 *zxdg_output_v1, i32 x, i32 y) { + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + monitor->x = x; + monitor->y = y; + RGFW_UNUSED(zxdg_output_v1); +} + +static void RGFW_xdg_output_logical_size(void *data, struct zxdg_output_v1 *zxdg_output_v1, i32 width, i32 height) { + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + float mon_float_width = (float) monitor->mode.w; + float mon_float_height = (float) monitor->mode.h; + + float scaleX = (mon_float_width / (float) width); + float scaleY = (mon_float_height / (float) height); + RGFW_UNUSED(scaleY); + + float dpi = scaleX * 96.0f; + + monitor->pixelRatio = dpi >= 192.0f ? 2.0f : 1.0f; + + /* under xwayland the monitor changes w & h when compositor scales it */ + monitor->mode.w = width; + monitor->mode.h = height; + RGFW_UNUSED(zxdg_output_v1); +} + + +static void RGFW_wl_output_handle_done(void* data, struct wl_output* output) { + RGFW_UNUSED(output); + + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + if (monitor->physW <= 0 || monitor->physH <= 0) { + monitor->physW = (i32) ((float)monitor->mode.w / 96.0f); + monitor->physH = (i32) ((float)monitor->mode.h / 96.0f); + } + + if (((RGFW_monitorNode*)data)->disconnected == RGFW_FALSE) { + return; + } + + ((RGFW_monitorNode*)data)->disconnected = RGFW_TRUE; + + RGFW_monitorCallback(_RGFW->root, monitor, RGFW_TRUE); +} + +static void RGFW_wl_create_outputs(struct wl_registry *const registry, u32 id) { + struct wl_output *output = wl_registry_bind(registry, id, &wl_output_interface, wl_display_get_version(_RGFW->wl_display) < 4 ? 3 : 4); + RGFW_monitorNode* node; + RGFW_monitor mon; + + if (!output) return; + + char RGFW_mon_default_name[10]; + + RGFW_SNPRINTF(RGFW_mon_default_name, sizeof(RGFW_mon_default_name), "monitor-%li", _RGFW->monitors.count); + RGFW_STRNCPY(mon.name, RGFW_mon_default_name, sizeof(mon.name) - 1); + mon.name[sizeof(mon.name) - 1] = '\0'; + + /* set in case compositor does not send one */ + /* or no xdg_output support */ + mon.scaleY = mon.scaleX = mon.pixelRatio = 1.0f; + + node = RGFW_monitors_add(&mon); + if (node == NULL) return; + + node->modeCount = 0; + node->disconnected = RGFW_TRUE; + node->id = id; + node->output = output; + + static const struct wl_output_listener wl_output_listener = { + .geometry = RGFW_wl_output_set_geometry, + .mode = RGFW_wl_output_handle_mode, + .done = RGFW_wl_output_handle_done, + .scale = RGFW_wl_output_set_scale, + .name = RGFW_wl_output_set_name, + .description = (void (*)(void *, struct wl_output *, const char *))&RGFW_doNothing + }; + + /* the wl_output will have a reference to the node */ + wl_output_set_user_data(output, node); + + /* pass the monitor so we can access it in the callback functions */ + wl_output_add_listener(output, &wl_output_listener, node); + + if (!_RGFW->xdg_output_manager) + return; /* compositor does not support it */ + + static const struct zxdg_output_v1_listener xdg_output_listener = { + .name = (void (*)(void *,struct zxdg_output_v1 *, const char *))&RGFW_doNothing, + .done = (void (*)(void *,struct zxdg_output_v1 *))&RGFW_doNothing, + .description = (void (*)(void *,struct zxdg_output_v1 *, const char *))&RGFW_doNothing, + .logical_position = RGFW_xdg_output_logical_pos, + .logical_size = RGFW_xdg_output_logical_size + }; + + node->xdg_output = zxdg_output_manager_v1_get_xdg_output(_RGFW->xdg_output_manager, node->output); + zxdg_output_v1_add_listener(node->xdg_output, &xdg_output_listener, node); +} + +static void RGFW_wl_surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *output) { + RGFW_UNUSED(wl_surface); + + RGFW_window* win = (RGFW_window*)data; + RGFW_monitorNode* node = wl_output_get_user_data(output); + if (node == NULL) return; + + win->src.active_monitor = node; + + if (win->internal.flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); +} + +static void RGFW_wl_data_source_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, i32 fd) { + RGFW_UNUSED(data); RGFW_UNUSED(wl_data_source); + + // a client can accept our clipboard + if (RGFW_STRNCMP(mime_type, "text/plain;charset=utf-8", 25) == 0) { + // do not write \0 + write(fd, _RGFW->clipboard, _RGFW->clipboard_len - 1); + } + + close(fd); +} + +static void RGFW_wl_data_source_cancelled(void *data, struct wl_data_source *wl_data_source) { + + RGFW_info* RGFW = (RGFW_info*)data; + + if (RGFW->kbOwner->src.data_source == wl_data_source) { + RGFW->kbOwner->src.data_source = NULL; + } + + wl_data_source_destroy(wl_data_source); + +} + +static void RGFW_wl_data_device_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *wl_data_offer) { + + RGFW_UNUSED(data); RGFW_UNUSED(wl_data_device); + static const struct wl_data_offer_listener wl_data_offer_listener = { + .offer = (void (*)(void *data, struct wl_data_offer *wl_data_offer, const char *))RGFW_doNothing, + .source_actions = (void (*)(void *data, struct wl_data_offer *wl_data_offer, u32 dnd_action))RGFW_doNothing, + .action = (void (*)(void *data, struct wl_data_offer *wl_data_offer, u32 dnd_action))RGFW_doNothing + }; + wl_data_offer_add_listener(wl_data_offer, &wl_data_offer_listener, NULL); +} + +static void RGFW_wl_data_device_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *wl_data_offer) { + RGFW_UNUSED(data); RGFW_UNUSED(wl_data_device); + /* Clipboard is empty */ + if (wl_data_offer == NULL) { + return; + } + + int pfds[2]; + pipe(pfds); + + wl_data_offer_receive(wl_data_offer, "text/plain;charset=utf-8", pfds[1]); + close(pfds[1]); + + wl_display_roundtrip(_RGFW->wl_display); + + char buf[1024]; + + ssize_t n = read(pfds[0], buf, sizeof(buf)); + + _RGFW->clipboard = (char*)RGFW_ALLOC((size_t)n); + RGFW_ASSERT(_RGFW->clipboard != NULL); + RGFW_STRNCPY(_RGFW->clipboard, buf, (size_t)n); + + _RGFW->clipboard_len = (size_t)n + 1; + + close(pfds[0]); + + wl_data_offer_destroy(wl_data_offer); + +} + +static void RGFW_wl_global_registry_handler(void* data, struct wl_registry *registry, u32 id, const char *interface, u32 version) { + + static struct wl_seat_listener seat_listener = {&RGFW_wl_seat_capabilities, (void (*)(void *, struct wl_seat *, const char *))&RGFW_doNothing}; + static const struct wl_shm_listener shm_listener = { .format = RGFW_wl_shm_format_handler }; + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_UNUSED(version); + + if (RGFW_STRNCMP(interface, "wl_compositor", 16) == 0) { + RGFW->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 4); + } else if (RGFW_STRNCMP(interface, "xdg_wm_base", 12) == 0) { + RGFW->xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); + } else if (RGFW_STRNCMP(interface, zxdg_decoration_manager_v1_interface.name, 255) == 0) { + RGFW->decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, zwp_pointer_constraints_v1_interface.name, 255) == 0) { + RGFW->constraint_manager = wl_registry_bind(registry, id, &zwp_pointer_constraints_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, zwp_relative_pointer_manager_v1_interface.name, 255) == 0) { + RGFW->relative_pointer_manager = wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, xdg_toplevel_icon_manager_v1_interface.name, 255) == 0) { + RGFW->icon_manager = wl_registry_bind(registry, id, &xdg_toplevel_icon_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, "wl_shm", 7) == 0) { + RGFW->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); + wl_shm_add_listener(RGFW->shm, &shm_listener, RGFW); + } else if (RGFW_STRNCMP(interface,"wl_seat", 8) == 0) { + RGFW->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(RGFW->seat, &seat_listener, RGFW); + } else if (RGFW_STRNCMP(interface, zxdg_output_manager_v1_interface.name, 255) == 0) { + RGFW->xdg_output_manager = wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface,"wl_output", 10) == 0) { + RGFW_wl_create_outputs(registry, id); + } else if (RGFW_STRNCMP(interface,"wl_data_device_manager", 23) == 0) { + RGFW->data_device_manager = wl_registry_bind(registry, id, &wl_data_device_manager_interface, 1); + } +} + +static void RGFW_wl_global_registry_remove(void* data, struct wl_registry *registry, u32 id) { + RGFW_UNUSED(data); RGFW_UNUSED(registry); + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_monitorNode* prev = RGFW->monitors.list.head; + RGFW_monitorNode* node = NULL; + if (prev == NULL) return; + + if (prev->id != id) { + /* find the first node that has a matching id */ + while(prev->next != NULL && prev->next->id != id) { + prev = prev->next; + } + + if (prev->next == NULL) return; + node = prev->next; + } else { + node = prev; + } + + if (node->output) { + wl_output_destroy(node->output); + } + + if (node->xdg_output) { + zxdg_output_v1_destroy(node->xdg_output); + } + + if (node->modeCount) { + RGFW_FREE(node->modes); + node->modeCount = 0; + } + + RGFW_monitorCallback(_RGFW->root, &node->mon, RGFW_FALSE); + RGFW_monitors_remove(node, prev); +} + +static void RGFW_wl_randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + + int i; + for (i = 0; i < 6; i++) { + buf[i] = (char)('A'+(r&15)+(r&16)*2); + r >>= 5; + } +} + +static int RGFW_wl_anonymous_shm_open(void) { + char name[] = "/RGFW-wayland-XXXXXX"; + int retries = 100; + + do { + RGFW_wl_randname(name + RGFW_unix_stringlen(name) - 6); + + --retries; + /* shm_open guarantees that O_CLOEXEC is set */ + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +static int RGFW_wl_create_shm_file(off_t size) { + int fd = RGFW_wl_anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +i32 RGFW_initPlatform_Wayland(void) { + _RGFW->wl_display = wl_display_connect(NULL); + if (_RGFW->wl_display == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, "Failed to load Wayland display"); + return -1; + } + + _RGFW->compositor = NULL; + static const struct wl_registry_listener registry_listener = { + .global = RGFW_wl_global_registry_handler, + .global_remove = RGFW_wl_global_registry_remove, + }; + + _RGFW->registry = wl_display_get_registry(_RGFW->wl_display); + wl_registry_add_listener(_RGFW->registry, ®istry_listener, _RGFW); + + wl_display_roundtrip(_RGFW->wl_display); /* bind to globals */ + + if (_RGFW->compositor == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, "Can't find compositor."); + return 1; + } + + if (_RGFW->wl_cursor_theme == NULL) { + _RGFW->wl_cursor_theme = wl_cursor_theme_load(NULL, 24, _RGFW->shm); + _RGFW->cursor_surface = wl_compositor_create_surface(_RGFW->compositor); + } + + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); + + static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = RGFW_wl_xdg_wm_base_ping_handler, + }; + + xdg_wm_base_add_listener(_RGFW->xdg_wm_base, &xdg_wm_base_listener, NULL); + + _RGFW->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + static const struct wl_data_device_listener wl_data_device_listener = { + .data_offer = RGFW_wl_data_device_data_offer, + .enter = (void (*)(void *, struct wl_data_device *, u32, struct wl_surface*, wl_fixed_t, wl_fixed_t, struct wl_data_offer *))&RGFW_doNothing, + .leave = (void (*)(void *, struct wl_data_device *))&RGFW_doNothing, + .motion = (void (*)(void *, struct wl_data_device *, u32, wl_fixed_t, wl_fixed_t))&RGFW_doNothing, + .drop = (void (*)(void *, struct wl_data_device *))&RGFW_doNothing, + .selection = RGFW_wl_data_device_selection + }; + + if (_RGFW->seat && _RGFW->data_device_manager) { + _RGFW->data_device = wl_data_device_manager_get_data_device(_RGFW->data_device_manager, _RGFW->seat); + wl_data_device_add_listener(_RGFW->data_device, &wl_data_device_listener, NULL); + } + + return 0; +} + +void RGFW_deinitPlatform_Wayland(void) { + if (_RGFW->clipboard) { + RGFW_FREE(_RGFW->clipboard); + _RGFW->clipboard = NULL; + } + + if (_RGFW->wl_pointer) { + wl_pointer_destroy(_RGFW->wl_pointer); + } + if (_RGFW->wl_keyboard) { + wl_keyboard_destroy(_RGFW->wl_keyboard); + } + + wl_registry_destroy(_RGFW->registry); + if (_RGFW->decoration_manager != NULL) + zxdg_decoration_manager_v1_destroy(_RGFW->decoration_manager); + if (_RGFW->relative_pointer_manager != NULL) { + zwp_relative_pointer_manager_v1_destroy(_RGFW->relative_pointer_manager); + } + + if (_RGFW->relative_pointer) { + zwp_relative_pointer_v1_destroy(_RGFW->relative_pointer); + } + + if (_RGFW->constraint_manager != NULL) { + zwp_pointer_constraints_v1_destroy(_RGFW->constraint_manager); + } + + if (_RGFW->xdg_output_manager != NULL) + if (_RGFW->icon_manager != NULL) { + xdg_toplevel_icon_manager_v1_destroy(_RGFW->icon_manager); + } + + if (_RGFW->xdg_output_manager) { + zxdg_output_manager_v1_destroy(_RGFW->xdg_output_manager); + } + + if (_RGFW->data_device_manager) { + wl_data_device_manager_destroy(_RGFW->data_device_manager); + } + + if (_RGFW->data_device) { + wl_data_device_destroy(_RGFW->data_device); + } + + if (_RGFW->wl_cursor_theme != NULL) { + wl_cursor_theme_destroy(_RGFW->wl_cursor_theme); + } + + RGFW_freeMouse(_RGFW->hiddenMouse); + + RGFW_monitorNode* node = _RGFW->monitors.list.head; + + while (node != NULL) { + if (node->output) { + wl_output_destroy(node->output); + } + + if (node->xdg_output) { + zxdg_output_v1_destroy(node->xdg_output); + } + + _RGFW->monitors.count -= 1; + node = node->next; + + } + + wl_surface_destroy(_RGFW->cursor_surface); + wl_shm_destroy(_RGFW->shm); + wl_seat_release(_RGFW->seat); + xdg_wm_base_destroy(_RGFW->xdg_wm_base); + wl_compositor_destroy(_RGFW->compositor); + wl_display_disconnect(_RGFW->wl_display); +} + +RGFW_bool RGFW_FUNC(RGFW_createSurfacePtr) (u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoBuffer, "Creating a 4 channel buffer"); + + u32 size = (u32)(surface->w * surface->h * 4); + int fd = RGFW_wl_create_shm_file(size); + if (fd < 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create a buffer."); + return RGFW_FALSE; + } + + surface->native.pool = wl_shm_create_pool(_RGFW->shm, fd, (i32)size); + + surface->native.buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (surface->native.buffer == MAP_FAILED) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "mmap failed."); + return RGFW_FALSE; + } + + surface->native.fd = fd; + surface->native.format = RGFW_formatBGRA8; + return RGFW_TRUE; +} + +void RGFW_FUNC(RGFW_window_blitSurface) (RGFW_window* win, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + + surface->native.wl_buffer = wl_shm_pool_create_buffer(surface->native.pool, 0, RGFW_MIN(win->w, surface->w), RGFW_MIN(win->h, surface->h), (i32)surface->w * 4, WL_SHM_FORMAT_ARGB8888); + RGFW_copyImageData(surface->native.buffer, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); + + + wl_surface_attach(win->src.surface, surface->native.wl_buffer, 0, 0); + wl_surface_damage(win->src.surface, 0, 0, RGFW_MIN(win->w, surface->w), RGFW_MIN(win->h, surface->h)); + wl_surface_commit(win->src.surface); + + wl_buffer_destroy(surface->native.wl_buffer); +} + +void RGFW_FUNC(RGFW_surface_freePtr) (RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + + wl_shm_pool_destroy(surface->native.pool); + close(surface->native.fd); + + munmap(surface->native.buffer, (size_t)(surface->w * surface->h * 4)); +} + +void RGFW_FUNC(RGFW_window_setBorder) (RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + + /* for now just toggle between SSD & CSD depending on the bool */ + if (_RGFW->decoration_manager != NULL) { + zxdg_toplevel_decoration_v1_set_mode(win->src.decoration, (border ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE)); + } +} + +void RGFW_FUNC(RGFW_window_setRawMouseModePlatform) (RGFW_window* win, RGFW_bool state) { + RGFW_ASSERT(win); + if (_RGFW->relative_pointer_manager == NULL) return; + + if (state == RGFW_FALSE) { + if (_RGFW->relative_pointer != NULL) + zwp_relative_pointer_v1_destroy(_RGFW->relative_pointer); + _RGFW->relative_pointer = NULL; + return; + } + + if (_RGFW->relative_pointer != NULL) return; + + _RGFW->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(_RGFW->relative_pointer_manager, _RGFW->wl_pointer); + + static const struct zwp_relative_pointer_v1_listener relative_motion_listener = { + .relative_motion = RGFW_wl_relative_pointer_motion + }; + + zwp_relative_pointer_v1_add_listener(_RGFW->relative_pointer, &relative_motion_listener, _RGFW); +} + +void RGFW_FUNC(RGFW_window_captureMousePlatform) (RGFW_window* win, RGFW_bool state) { + RGFW_ASSERT(win); + + /* compositor has no support or window already is locked do nothing */ + if (_RGFW->constraint_manager == NULL) return; + + if (state == RGFW_FALSE) { + if (win->src.locked_pointer != NULL) + zwp_locked_pointer_v1_destroy(win->src.locked_pointer); + win->src.locked_pointer = NULL; + return; + } + + + if (win->src.locked_pointer != NULL) return; + win->src.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(_RGFW->constraint_manager, win->src.surface, _RGFW->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + + static const struct zwp_locked_pointer_v1_listener locked_listener = { + .locked = RGFW_wl_pointer_locked, + .unlocked = (void (*)(void *, struct zwp_locked_pointer_v1 *))RGFW_doNothing + }; + + zwp_locked_pointer_v1_add_listener(win->src.locked_pointer, &locked_listener, _RGFW); +} + +RGFW_window* RGFW_FUNC(RGFW_createWindowPlatform) (const char* name, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, "RGFW Wayland support is experimental"); + + static const struct xdg_surface_listener xdg_surface_listener = { + .configure = RGFW_wl_xdg_surface_configure_handler, + }; + + static const struct wl_surface_listener wl_surface_listener = { + .enter = RGFW_wl_surface_enter, + .leave = (void (*)(void *, struct wl_surface *, struct wl_output *))&RGFW_doNothing, + .preferred_buffer_scale = (void (*)(void *, struct wl_surface *, i32))&RGFW_doNothing, + .preferred_buffer_transform = (void (*)(void *, struct wl_surface *, u32))&RGFW_doNothing + }; + + win->src.surface = wl_compositor_create_surface(_RGFW->compositor); + wl_surface_add_listener(win->src.surface, &wl_surface_listener, win); + + /* create a surface for a custom cursor */ + win->src.custom_cursor_surface = wl_compositor_create_surface(_RGFW->compositor); + + win->src.xdg_surface = xdg_wm_base_get_xdg_surface(_RGFW->xdg_wm_base, win->src.surface); + xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, win); + + xdg_wm_base_set_user_data(_RGFW->xdg_wm_base, win); + + win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); + + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->w, win->h); + + if (!(win->internal.flags & RGFW_windowTransparent)) { /* no transparency */ + RGFW_wl_setOpaque(win); + } + + static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = RGFW_wl_xdg_toplevel_configure_handler, + .close = RGFW_wl_xdg_toplevel_close_handler, + }; + + xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, win); + + /* compositor supports both SSD & CSD + So choose accordingly + */ + if (_RGFW->decoration_manager) { + u32 decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + _RGFW->decoration_manager, win->src.xdg_toplevel); + + static const struct zxdg_toplevel_decoration_v1_listener xdg_decoration_listener = { + .configure = RGFW_wl_xdg_decoration_configure_handler + }; + + zxdg_toplevel_decoration_v1_add_listener(win->src.decoration, &xdg_decoration_listener, win); + + /* we want no decorations */ + if ((flags & RGFW_windowNoBorder)) { + decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } + + zxdg_toplevel_decoration_v1_set_mode(win->src.decoration, decoration_mode); + + /* no xdg_decoration support */ + } else if (!(flags & RGFW_windowNoBorder)) { + /* TODO, some fallback */ + #ifdef RGFW_LIBDECOR + static struct libdecor_interface interface = { + .error = NULL, + }; + + static struct libdecor_frame_interface frameInterface = {0}; /*= { + RGFW_wl_handle_configure, + RGFW_wl_handle_close, + RGFW_wl_handle_commit, + RGFW_wl_handle_dismiss_popup, + };*/ + + win->src.decorContext = libdecor_new(_RGFW->wl_display, &interface); + if (win->src.decorContext) { + struct libdecor_frame *frame = libdecor_decorate(win->src.decorContext, win->src.surface, &frameInterface, win); + if (!frame) { + libdecor_unref(win->src.decorContext); + win->src.decorContext = NULL; + } else { + libdecor_frame_set_app_id(frame, "my-libdecor-app"); + libdecor_frame_set_title(frame, "My Libdecor Window"); + } + } + #endif + } + + if (_RGFW->icon_manager != NULL) { + /* set the default wayland icon */ + xdg_toplevel_icon_manager_v1_set_icon(_RGFW->icon_manager, win->src.xdg_toplevel, NULL); + } + + wl_surface_commit(win->src.surface); + wl_display_dispatch(_RGFW->wl_display); + RGFW_UNUSED(name); + + return win; +} + +RGFW_bool RGFW_FUNC(RGFW_getGlobalMouse) (i32* x, i32* y) { + RGFW_init(); + if (x) *x = 0; + if (y) *y = 0; + return RGFW_FALSE; +} + +u8 RGFW_FUNC(RGFW_rgfwToKeyChar)(u32 key) { + return (u8)key; +} + +void RGFW_FUNC(RGFW_pollEvents) (void) { + RGFW_resetPrevState(); + + /* send buffered requests to compositor */ + while (wl_display_flush(_RGFW->wl_display) == -1) { + /* compositor not responding to new requests */ + /* so let's dispatch some events so the compositor responds */ + if (errno == EAGAIN) { + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; + } + } else { + return; + } + } + + /* read the events; if empty this reads from the */ + /* wayland file descriptor */ + if (wl_display_dispatch(_RGFW->wl_display) == -1) { + return; + } + +} + +void RGFW_FUNC(RGFW_window_move) (RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + win->x = x; + win->y = y; +} + + +void RGFW_FUNC(RGFW_window_resize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + win->w = w; + win->h = h; + if (_RGFW->compositor) { + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->w, win->h); + #ifdef RGFW_OPENGL + if (win->src.ctx.egl) + wl_egl_window_resize(win->src.ctx.egl->eglWindow, (i32)w, (i32)h, 0, 0); + #endif + } +} + +void RGFW_FUNC(RGFW_window_setAspectRatio) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + + if (w == 0 && h == 0) + return; + xdg_toplevel_set_max_size(win->src.xdg_toplevel, (i32)w, (i32)h); +} + +void RGFW_FUNC(RGFW_window_setMinSize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + xdg_toplevel_set_min_size(win->src.xdg_toplevel, w, h); +} + +void RGFW_FUNC(RGFW_window_setMaxSize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + xdg_toplevel_set_max_size(win->src.xdg_toplevel, w, h); +} + +void RGFW_toggleWaylandMaximized(RGFW_window* win, RGFW_bool maximized) { + win->src.maximized = maximized; + if (maximized) { + xdg_toplevel_set_maximized(win->src.xdg_toplevel); + } else { + xdg_toplevel_unset_maximized(win->src.xdg_toplevel); + } +} + +void RGFW_FUNC(RGFW_window_maximize) (RGFW_window* win) { + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + RGFW_toggleWaylandMaximized(win, 1); + return; +} + +void RGFW_FUNC(RGFW_window_focus)(RGFW_window* win) { + RGFW_ASSERT(win); +} + +void RGFW_FUNC(RGFW_window_raise)(RGFW_window* win) { + RGFW_ASSERT(win); +} + +void RGFW_FUNC(RGFW_window_setFullscreen)(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen) { + + win->internal.flags |= RGFW_windowFullscreen; + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + xdg_toplevel_set_fullscreen(win->src.xdg_toplevel, NULL); /* let the compositor decide */ + } else { + win->internal.flags &= ~(u32)RGFW_windowFullscreen; + xdg_toplevel_unset_fullscreen(win->src.xdg_toplevel); + } + +} + +void RGFW_FUNC(RGFW_window_setFloating) (RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(floating); +} + +void RGFW_FUNC(RGFW_window_setOpacity) (RGFW_window* win, u8 opacity) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(opacity); +} + +void RGFW_FUNC(RGFW_window_minimize)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if (RGFW_window_isMaximized(win)) return; + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + win->src.minimized = RGFW_TRUE; + xdg_toplevel_set_minimized(win->src.xdg_toplevel); +} + +void RGFW_FUNC(RGFW_window_restore)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_toggleWaylandMaximized(win, RGFW_FALSE); + + RGFW_window_move(win, win->internal.oldX, win->internal.oldY); + RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); + + RGFW_window_show(win); + RGFW_window_move(win, win->internal.oldX, win->internal.oldY); + RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); + + RGFW_window_show(win); +} + +RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { + return (!RGFW_window_isFullscreen(win) && !RGFW_window_isMaximized(win)); +} + +void RGFW_FUNC(RGFW_window_setName) (RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + if (name == NULL) name = "\0"; + + if (_RGFW->compositor) + xdg_toplevel_set_title(win->src.xdg_toplevel, name); +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_FUNC(RGFW_window_setMousePassthrough) (RGFW_window* win, RGFW_bool passthrough) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(passthrough); +} +#endif /* RGFW_NO_PASSTHROUGH */ + +RGFW_bool RGFW_FUNC(RGFW_window_setIconEx) (RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(type); + + if (_RGFW->icon_manager == NULL || w != h) return RGFW_FALSE; + + if (win->src.icon) { + xdg_toplevel_icon_v1_destroy(win->src.icon); + win->src.icon= NULL; + } + + RGFW_surface* surface = RGFW_createSurface(data, w, h, format); + + if (surface == NULL) return RGFW_FALSE; + + RGFW_copyImageData(surface->native.buffer, RGFW_MIN(w, surface->w), RGFW_MIN(h, surface->h), surface->native.format, surface->data, surface->format); + + win->src.icon = xdg_toplevel_icon_manager_v1_create_icon(_RGFW->icon_manager); + xdg_toplevel_icon_v1_add_buffer(win->src.icon, surface->native.wl_buffer, 1); + xdg_toplevel_icon_manager_v1_set_icon(_RGFW->icon_manager, win->src.xdg_toplevel, win->src.icon); + + RGFW_surface_free(surface); + return RGFW_TRUE; +} + +RGFW_mouse* RGFW_FUNC(RGFW_loadMouse)(u8* data, i32 w, i32 h, RGFW_format format) { + + RGFW_surface *mouse_surface = RGFW_createSurface(data, w, h, format); + + if (mouse_surface == NULL) return NULL; + + RGFW_copyImageData(mouse_surface->native.buffer, RGFW_MIN(w, mouse_surface->w), RGFW_MIN(h, mouse_surface->h), mouse_surface->native.format, mouse_surface->data, mouse_surface->format); + + return (void*) mouse_surface; +} + +void RGFW_FUNC(RGFW_window_setMouse)(RGFW_window* win, RGFW_mouse* mouse) { + RGFW_ASSERT(win); RGFW_ASSERT(mouse); + RGFW_surface *mouse_surface = (RGFW_surface*)mouse; + + win->src.using_custom_cursor = RGFW_TRUE; + + struct wl_buffer *mouse_buffer = mouse_surface->native.wl_buffer; + + wl_surface_attach(win->src.custom_cursor_surface, mouse_buffer, 0, 0); + wl_surface_damage(win->src.custom_cursor_surface, 0, 0, mouse_surface->w, mouse_surface->h); + wl_surface_commit(win->src.custom_cursor_surface); + +} + +void RGFW_FUNC(RGFW_freeMouse)(RGFW_mouse* mouse) { + if (mouse != NULL) { + RGFW_surface_free((RGFW_surface*)mouse); + } +} + +void RGFW_FUNC(RGFW_window_moveMouse)(RGFW_window* win, i32 x, i32 y) { + RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); +} + +RGFW_bool RGFW_FUNC(RGFW_window_setMouseDefault)(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +RGFW_bool RGFW_FUNC(RGFW_window_setMouseStandard)(RGFW_window* win, u8 mouse) { + RGFW_ASSERT(win != NULL); + + char* cursorName = NULL; + switch (mouse) { + case RGFW_mouseNormal: cursorName = (char*)"left_ptr"; break; + case RGFW_mouseArrow: cursorName = (char*)"left_ptr"; break; + case RGFW_mouseIbeam: cursorName = (char*)"xterm"; break; + case RGFW_mouseCrosshair: cursorName = (char*)"crosshair"; break; + case RGFW_mousePointingHand: cursorName = (char*)"hand2"; break; + case RGFW_mouseResizeEW: cursorName = (char*)"sb_h_double_arrow"; break; + case RGFW_mouseResizeNS: cursorName = (char*)"sb_v_double_arrow"; break; + case RGFW_mouseResizeNWSE: cursorName = (char*)"top_left_corner"; break; /* or fd_double_arrow */ + case RGFW_mouseResizeNESW: cursorName = (char*)"top_right_corner"; break; /* or bd_double_arrow */ + case RGFW_mouseResizeNW: cursorName = (char*)"top_left_corner"; break; + case RGFW_mouseResizeN: cursorName = (char*)"top_side"; break; + case RGFW_mouseResizeNE: cursorName = (char*)"top_right_corner"; break; + case RGFW_mouseResizeE: cursorName = (char*)"right_side"; break; + case RGFW_mouseResizeSE: cursorName = (char*)"bottom_right_corner"; break; + case RGFW_mouseResizeS: cursorName = (char*)"bottom_side"; break; + case RGFW_mouseResizeSW: cursorName = (char*)"bottom_left_corner"; break; + case RGFW_mouseResizeW: cursorName = (char*)"left_side"; break; + case RGFW_mouseResizeAll: cursorName = (char*)"fleur"; break; + case RGFW_mouseNotAllowed: cursorName = (char*)"not-allowed"; break; + case RGFW_mouseWait: cursorName = (char*)"watch"; break; + case RGFW_mouseProgress: cursorName = (char*)"watch"; break; + default: return RGFW_FALSE; + } + + win->src.using_custom_cursor = RGFW_FALSE; + + struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(_RGFW->wl_cursor_theme, cursorName); + struct wl_cursor_image* cursor_image = wlcursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(cursor_image); + wl_pointer_set_cursor(_RGFW->wl_pointer, _RGFW->mouse_enter_serial, _RGFW->cursor_surface, (i32)cursor_image->hotspot_x, (i32)cursor_image->hotspot_y); + wl_surface_attach(_RGFW->cursor_surface, cursor_buffer, 0, 0); + wl_surface_damage(_RGFW->cursor_surface, 0, 0, (i32)cursor_image->width, (i32)cursor_image->height); + wl_surface_commit(_RGFW->cursor_surface); + return RGFW_TRUE; +} + +void RGFW_FUNC(RGFW_window_hide) (RGFW_window* win) { + wl_surface_attach(win->src.surface, NULL, 0, 0); + wl_surface_commit(win->src.surface); + win->internal.flags |= RGFW_windowHide; +} + +void RGFW_FUNC(RGFW_window_show) (RGFW_window* win) { + win->internal.flags &= ~(u32)RGFW_windowHide; + if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + /* wl_surface_attach(win->src.surface, win->x, win->y, win->w, win->h, 0, 0); */ + wl_surface_commit(win->src.surface); +} + +void RGFW_FUNC(RGFW_window_flash) (RGFW_window* win, RGFW_flashRequest request) { + if (RGFW_window_isInFocus(win) && request) { + return; + } +} + +RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr) (char* str, size_t strCapacity) { + + RGFW_UNUSED(strCapacity); + + if (str != NULL) + RGFW_STRNCPY(str, _RGFW->clipboard, _RGFW->clipboard_len - 1); + _RGFW->clipboard[_RGFW->clipboard_len - 1] = '\0'; + return (RGFW_ssize_t)_RGFW->clipboard_len - 1; +} + +void RGFW_FUNC(RGFW_writeClipboard) (const char* text, u32 textLen) { + + // compositor does not support wl_data_device_manager + // clients cannot read rgfw's clipboard + if (_RGFW->data_device_manager == NULL) return; + // clear the clipboard + if (_RGFW->clipboard) + RGFW_FREE(_RGFW->clipboard); + + // set the contents + _RGFW->clipboard = (char*)RGFW_ALLOC(textLen); + RGFW_ASSERT(_RGFW->clipboard != NULL); + RGFW_STRNCPY(_RGFW->clipboard, text, textLen - 1); + _RGFW->clipboard[textLen - 1] = '\0'; + _RGFW->clipboard_len = textLen; + + // means we already wrote to the clipboard + // so destroy it to create a new one + RGFW_window* win = _RGFW->kbOwner; + + if (win->src.data_source != NULL) { + wl_data_source_destroy(win->src.data_source); + win->src.data_source = NULL; + } + + // advertise to other clients that we offer text + win->src.data_source = wl_data_device_manager_create_data_source(_RGFW->data_device_manager); + + // basic error checking + if (win->src.data_source == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "Could not create clipboard data source"); + return; + } + wl_data_source_offer(win->src.data_source , "text/plain;charset=utf-8"); + + // needed RGFW_doNothing because wayland will call the functions + // if not set they are random data that lead to a crash + static const struct wl_data_source_listener data_source_listener = { + .target = (void (*)(void *, struct wl_data_source *, const char *))&RGFW_doNothing, + .action = (void (*)(void *, struct wl_data_source *, u32))&RGFW_doNothing, + .dnd_drop_performed = (void (*)(void *, struct wl_data_source *))&RGFW_doNothing, + .dnd_finished = (void (*)(void *, struct wl_data_source *))&RGFW_doNothing, + .send = RGFW_wl_data_source_send, + .cancelled = RGFW_wl_data_source_cancelled + }; + + wl_data_source_add_listener(win->src.data_source, &data_source_listener, _RGFW); + +} + +RGFW_bool RGFW_FUNC(RGFW_window_isHidden) (RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return RGFW_FALSE; +} + +RGFW_bool RGFW_FUNC(RGFW_window_isMinimized) (RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return win->src.minimized; +} + +RGFW_bool RGFW_FUNC(RGFW_window_isMaximized) (RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return win->src.maximized; +} + +void RGFW_FUNC(RGFW_pollMonitors) (void) { + _RGFW->monitors.primary = _RGFW->monitors.list.head; +} + + +RGFW_bool RGFW_FUNC(RGFW_monitor_getWorkarea) (RGFW_monitor* monitor, i32* x, i32* y, i32* width, i32* height) { + /* NOTE: Wayland has no way to get the actual workarea as far as I'm aware :( */ + if (x) *x = monitor->x; + if (y) *y = monitor->y; + if (width) *width = monitor->mode.w; + if (height) *height = monitor->mode.h; + return RGFW_TRUE; +} + +size_t RGFW_FUNC(RGFW_monitor_getModesPtr) (RGFW_monitor* monitor, RGFW_monitorMode** modes) { + if (modes) { + RGFW_MEMCPY((*modes), monitor->node->modes, monitor->node->modeCount * sizeof(RGFW_monitorMode)); + } + + return monitor->node->modeCount; +} + +size_t RGFW_FUNC(RGFW_monitor_getGammaRampPtr) (RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + RGFW_UNUSED(monitor); RGFW_UNUSED(ramp); + return 0; +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_setGammaRamp) (RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + RGFW_UNUSED(monitor); RGFW_UNUSED(ramp); + return RGFW_FALSE; +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_requestMode) (RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request) { + for (size_t i = 0; i < mon->node->modeCount; i++) { + if (RGFW_monitorModeCompare(mode, &mon->node->modes[i], request) == RGFW_FALSE) { + continue; + } + + RGFW_monitor_setMode(mon, &mon->node->modes[i]); + return RGFW_TRUE; + } + + return RGFW_FALSE; +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_setMode) (RGFW_monitor* mon, RGFW_monitorMode* mode) { + RGFW_UNUSED(mon); RGFW_UNUSED(mode); + return RGFW_FALSE; +} + +RGFW_monitor* RGFW_FUNC(RGFW_window_getMonitor) (RGFW_window* win) { + RGFW_ASSERT(win); + if (win->src.active_monitor == NULL) { + /* TODO: fix race condition [probably a problem with wayland] */ + return RGFW_getPrimaryMonitor(); + } + + return &win->src.active_monitor->mon; +} + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_FUNC(RGFW_extensionSupportedPlatform_OpenGL) (const char * extension, size_t len) { return RGFW_extensionSupportedPlatform_EGL(extension, len); } +RGFW_proc RGFW_FUNC(RGFW_getProcAddress_OpenGL) (const char* procname) { return RGFW_getProcAddress_EGL(procname); } + + +RGFW_bool RGFW_FUNC(RGFW_window_createContextPtr_OpenGL)(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + RGFW_bool out = RGFW_window_createContextPtr_EGL(win, &ctx->egl, hints); + win->src.gfxType = RGFW_gfxNativeOpenGL; + + RGFW_window_swapInterval_OpenGL(win, 0); + return out; +} +void RGFW_FUNC(RGFW_window_deleteContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* ctx) { RGFW_window_deleteContextPtr_EGL(win, &ctx->egl); win->src.ctx.native = NULL; } + +void RGFW_FUNC(RGFW_window_makeCurrentContext_OpenGL) (RGFW_window* win) { RGFW_window_makeCurrentContext_EGL(win); } +void* RGFW_FUNC(RGFW_getCurrentContext_OpenGL) (void) { return RGFW_getCurrentContext_EGL(); } +void RGFW_FUNC(RGFW_window_swapBuffers_OpenGL) (RGFW_window* win) { RGFW_window_swapBuffers_EGL(win); } +void RGFW_FUNC(RGFW_window_swapInterval_OpenGL) (RGFW_window* win, i32 swapInterval) { RGFW_window_swapInterval_EGL(win, swapInterval); } +#endif /* RGFW_OPENGL */ + +void RGFW_FUNC(RGFW_window_closePlatform)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a window was freed"); + #ifdef RGFW_LIBDECOR + if (win->src.decorContext) + libdecor_unref(win->src.decorContext); + #endif + + if (win->src.decoration) { + zxdg_toplevel_decoration_v1_destroy(win->src.decoration); + } + + if (win->src.xdg_toplevel) { + xdg_toplevel_destroy(win->src.xdg_toplevel); + } + + wl_surface_destroy(win->src.custom_cursor_surface); + + if (win->src.locked_pointer) { + zwp_locked_pointer_v1_destroy(win->src.locked_pointer); + } + + if (win->src.icon) { + xdg_toplevel_icon_v1_destroy(win->src.icon); + } + + xdg_surface_destroy(win->src.xdg_surface); + wl_surface_destroy(win->src.surface); +} + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUSurfaceSourceWaylandSurface fromWl = {0}; + fromWl.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + fromWl.display = _RGFW->wl_display; + fromWl.surface = window->src.surface; + + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromWl.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + + + +#endif /* RGFW_WAYLAND */ +/* + End of Wayland defines +*/ + +/* + + Start of Windows defines + + +*/ + +#ifdef RGFW_WINDOWS +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif + +#ifndef OEMRESOURCE + #define OEMRESOURCE +#endif + +#include + +#ifndef OCR_NORMAL +#define OCR_NORMAL 32512 +#define OCR_IBEAM 32513 +#define OCR_WAIT 32514 +#define OCR_CROSS 32515 +#define OCR_UP 32516 +#define OCR_SIZENWSE 32642 +#define OCR_SIZENESW 32643 +#define OCR_SIZEWE 32644 +#define OCR_SIZENS 32645 +#define OCR_SIZEALL 32646 +#define OCR_NO 32648 +#define OCR_HAND 32649 +#define OCR_APPSTARTING 32650 +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef WM_DPICHANGED +#define WM_DPICHANGED 0x02E0 +#endif + +RGFW_bool RGFW_createUTF8FromWideStringWin32(const WCHAR* source, char* out, size_t max); + +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 + +typedef int (*PFN_wglGetSwapIntervalEXT)(void); +PFN_wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc = NULL; +#define wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc + +/* these two wgl functions need to be preloaded */ +typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hglrc, const int *attribList); +PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; + +HMODULE RGFW_wgl_dll = NULL; + +#ifndef RGFW_NO_LOAD_WGL + typedef HGLRC(WINAPI* PFN_wglCreateContext)(HDC); + typedef BOOL(WINAPI* PFN_wglDeleteContext)(HGLRC); + typedef PROC(WINAPI* PFN_wglGetProcAddress)(LPCSTR); + typedef BOOL(WINAPI* PFN_wglMakeCurrent)(HDC, HGLRC); + typedef HDC(WINAPI* PFN_wglGetCurrentDC)(void); + typedef HGLRC(WINAPI* PFN_wglGetCurrentContext)(void); + typedef BOOL(WINAPI* PFN_wglShareLists)(HGLRC, HGLRC); + + PFN_wglCreateContext wglCreateContextSRC; + PFN_wglDeleteContext wglDeleteContextSRC; + PFN_wglGetProcAddress wglGetProcAddressSRC; + PFN_wglMakeCurrent wglMakeCurrentSRC; + PFN_wglGetCurrentDC wglGetCurrentDCSRC; + PFN_wglGetCurrentContext wglGetCurrentContextSRC; + PFN_wglShareLists wglShareListsSRC; + + #define wglCreateContext wglCreateContextSRC + #define wglDeleteContext wglDeleteContextSRC + #define wglGetProcAddress wglGetProcAddressSRC + #define wglMakeCurrent wglMakeCurrentSRC + #define wglGetCurrentDC wglGetCurrentDCSRC + #define wglGetCurrentContext wglGetCurrentContextSRC + #define wglShareLists wglShareListsSRC +#endif + +void* RGFW_window_getHWND(RGFW_window* win) { return win->src.window; } +void* RGFW_window_getHDC(RGFW_window* win) { return win->src.hdc; } + +#ifdef RGFW_OPENGL +RGFWDEF void RGFW_win32_loadOpenGLFuncs(HWND dummyWin); + +typedef HRESULT (APIENTRY* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats); +PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; + +typedef BOOL(APIENTRY* PFNWGLSWAPINTERVALEXTPROC)(int interval); +PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; +#endif + +#ifndef RGFW_NO_DWM +HMODULE RGFW_dwm_dll = NULL; +#ifndef _DWMAPI_H_ +typedef struct { DWORD dwFlags; int fEnable; HRGN hRgnBlur; int fTransitionOnMaximized;} DWM_BLURBEHIND; +#endif +typedef HRESULT (WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND, const DWM_BLURBEHIND*); +PFN_DwmEnableBlurBehindWindow DwmEnableBlurBehindWindowSRC = NULL; + +typedef HRESULT (WINAPI * PFN_DwmSetWindowAttribute)(HWND, DWORD, LPCVOID, DWORD); +PFN_DwmSetWindowAttribute DwmSetWindowAttributeSRC = NULL; +#endif +void RGFW_win32_makeWindowTransparent(RGFW_window* win); +void RGFW_win32_makeWindowTransparent(RGFW_window* win) { + if (!(win->internal.flags & RGFW_windowTransparent)) return; + + #ifndef RGFW_NO_DWM + if (DwmEnableBlurBehindWindowSRC != NULL) { + DWM_BLURBEHIND bb = {0, 0, 0, 0}; + bb.dwFlags = 0x1; + bb.fEnable = TRUE; + bb.hRgnBlur = NULL; + DwmEnableBlurBehindWindowSRC(win->src.window, &bb); + + } else + #endif + { + SetWindowLong(win->src.window, GWL_EXSTYLE, WS_EX_LAYERED); + SetLayeredWindowAttributes(win->src.window, 0, 128, LWA_ALPHA); + } +} + +RGFWDEF RGFW_bool RGFW_win32_getDarkModeState(void); +RGFW_bool RGFW_win32_getDarkModeState(void) { + u32 lightMode = 1; + DWORD len = sizeof(lightMode); + + RegGetValueW( + HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + L"AppsUseLightTheme", RRF_RT_REG_DWORD, NULL, &lightMode, &len + ); + + return (lightMode == 0); +} + +RGFWDEF void RGFW_win32_makeWindowDarkMode(RGFW_window* win, RGFW_bool state); +void RGFW_win32_makeWindowDarkMode(RGFW_window* win, RGFW_bool state) { + BOOL value = (state == RGFW_TRUE) ? TRUE : FALSE; + DwmSetWindowAttributeSRC(win->src.window, 20 /* DWMWA_USE_IMMERSIVE_DARK_MODE */, &value, sizeof(value)); +} + +LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + RGFW_window* win = (RGFW_window*)GetPropW(hWnd, L"RGFW"); + if (win == NULL) return DefWindowProcW(hWnd, message, wParam, lParam); + + static BYTE keyboardState[256]; + GetKeyboardState(keyboardState); + + RECT windowRect; + GetWindowRect(hWnd, &windowRect); + + switch (message) { + case WM_DISPLAYCHANGE: + RGFW_pollMonitors(); + break; + case WM_CLOSE: + case WM_QUIT: + RGFW_windowQuitCallback(win); + return 0; + case WM_ACTIVATE: { + RGFW_bool inFocus = RGFW_BOOL(LOWORD(wParam) != WA_INACTIVE); + RGFW_focusCallback(win, inFocus); + + return DefWindowProcW(hWnd, message, wParam, lParam); + } + case WM_MOVE: + if (win->internal.captureMouse) { + RGFW_window_captureMousePlatform(win, RGFW_TRUE); + } + + RGFW_windowMovedCallback(win, windowRect.left, windowRect.top); + return DefWindowProcW(hWnd, message, wParam, lParam); + case WM_SIZE: { + if (win->internal.captureMouse) { + RGFW_window_captureMousePlatform(win, RGFW_TRUE); + } + + if (win->src.aspectRatioW != 0 && win->src.aspectRatioH != 0) { + double aspectRatio = (double)win->src.aspectRatioW / win->src.aspectRatioH; + + int width = windowRect.right - windowRect.left; + int height = windowRect.bottom - windowRect.top; + int newHeight = (int)(width / aspectRatio); + int newWidth = (int)(height * aspectRatio); + + if (win->w > (i32)((windowRect.right - windowRect.left) - win->src.offsetW) || + win->h > (i32)((windowRect.bottom - windowRect.top) - win->src.offsetH)) + { + if (newHeight > height) windowRect.right = windowRect.left + newWidth; + else windowRect.bottom = windowRect.top + newHeight; + } else { + if (newHeight < height) windowRect.right = windowRect.left + newWidth; + else windowRect.bottom = windowRect.top + newHeight; + } + + RGFW_window_resize(win, (windowRect.right - windowRect.left) - win->src.offsetW, + (windowRect.bottom - windowRect.top) - win->src.offsetH); + } + + i32 w = (windowRect.right - windowRect.left) - (i32)win->src.offsetW; + i32 h = (windowRect.bottom - windowRect.top) - (i32)win->src.offsetH; + RGFW_windowResizedCallback(win, w, h); + RGFW_window_checkMode(win); + return DefWindowProcW(hWnd, message, wParam, lParam); + } + case WM_MOUSEACTIVATE: { + if (HIWORD(lParam) == WM_LBUTTONDOWN) { + if (LOWORD(lParam) != HTCLIENT) + win->src.actionFrame = RGFW_TRUE; + } + + break; + } + case WM_CAPTURECHANGED: { + if (lParam == 0 && win->src.actionFrame) { + RGFW_window_captureMousePlatform(win, win->internal.captureMouse); + win->src.actionFrame = RGFW_FALSE; + } + + break; + } + #ifndef RGFW_NO_DPI + case WM_DPICHANGED: { + const float scaleX = HIWORD(wParam) / (float) 96; + const float scaleY = LOWORD(wParam) / (float) 96; + + RGFW_scaleUpdatedCallback(win, scaleX, scaleY); + return DefWindowProcW(hWnd, message, wParam, lParam); + } + #endif + case WM_GETMINMAXINFO: { + MINMAXINFO* mmi = (MINMAXINFO*) lParam; + mmi->ptMinTrackSize.x = (LONG)(win->src.minSizeW + win->src.offsetW); + mmi->ptMinTrackSize.y = (LONG)(win->src.minSizeH + win->src.offsetH); + if (win->src.maxSizeW == 0 && win->src.maxSizeH == 0) + return DefWindowProcW(hWnd, message, wParam, lParam); + + mmi->ptMaxTrackSize.x = (LONG)(win->src.maxSizeW + win->src.offsetW); + mmi->ptMaxTrackSize.y = (LONG)(win->src.maxSizeH + win->src.offsetH); + return DefWindowProcW(hWnd, message, wParam, lParam); + } + case WM_PAINT: { + PAINTSTRUCT ps; + BeginPaint(hWnd, &ps); + RGFW_windowRefreshCallback(win); + EndPaint(hWnd, &ps); + + return DefWindowProcW(hWnd, message, wParam, lParam); + } + #if(_WIN32_WINNT >= 0x0600) + case WM_DWMCOMPOSITIONCHANGED: + case WM_DWMCOLORIZATIONCOLORCHANGED: + RGFW_win32_makeWindowTransparent(win); + break; + #endif + + case WM_ENTERSIZEMOVE: { + if (win->src.actionFrame) + RGFW_window_captureMousePlatform(win, win->internal.captureMouse); + + #ifdef RGFW_ADVANCED_SMOOTH_RESIZE + SetTimer(win->src.window, 1, USER_TIMER_MINIMUM, NULL); break; + #endif + break; + } + case WM_EXITSIZEMOVE: { + if (win->src.actionFrame) + RGFW_window_captureMousePlatform(win, win->internal.captureMouse); + + #ifdef RGFW_ADVANCED_SMOOTH_RESIZE + KillTimer(win->src.window, 1); break; + #endif + break; + } + case WM_TIMER: + RGFW_windowRefreshCallback(win); + break; + + case WM_NCLBUTTONDOWN: { + /* workaround for half-second pause when starting to move window + see: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + */ + POINT point = { 0, 0 }; + if (SendMessage(win->src.window, WM_NCHITTEST, wParam, lParam) != HTCAPTION || GetCursorPos(&point) == FALSE) + break; + + ScreenToClient(win->src.window, &point); + PostMessage(win->src.window, WM_MOUSEMOVE, 0, (u32)(point.x)|((u32)(point.y) << 16)); + break; + } + case WM_MOUSELEAVE: + RGFW_mouseNotifyCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, RGFW_FALSE); + break; + case WM_SYSKEYUP: case WM_KEYUP: { + if (!(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + i32 scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((UINT)wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + RGFW_key value = (u8)RGFW_apiKeyToRGFW((u32) scancode); + + if (wParam == VK_CONTROL) { + if (HIWORD(lParam) & KF_EXTENDED) + value = RGFW_controlR; + else value = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)wParam, (UINT)scancode, keyboardState, (wchar_t*)&charBuffer, 1, 0, NULL); + + u8 sym = (u8)charBuffer; + + RGFW_bool repeat = ((lParam & 0x40000000) != 0) || RGFW_window_isKeyDown(win, value); + + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + RGFW_keyCallback(win, value, sym, win->internal.mod, repeat, RGFW_FALSE); + break; + } + case WM_SYSKEYDOWN: case WM_KEYDOWN: { + if (!(win->internal.enabledEvents & RGFW_keyPressedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + i32 scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((u32)wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + RGFW_key value = (u8)RGFW_apiKeyToRGFW((u32) scancode); + if (wParam == VK_CONTROL) { + if (HIWORD(lParam) & KF_EXTENDED) + value = RGFW_controlR; + else value = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)wParam, (UINT)scancode, keyboardState, &charBuffer, 1, 0, NULL); + u8 sym = (u8)charBuffer; + + RGFW_bool repeat = ((lParam & 0x40000000) != 0) || RGFW_window_isKeyDown(win, value); + + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + RGFW_keyCallback(win, value, sym, win->internal.mod, repeat, 1); + break; + } + case WM_MOUSEMOVE: { + if (win->internal.mouseInside == RGFW_FALSE) { + RGFW_mouseNotifyCallback(win, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), RGFW_TRUE); + } + + if ((win->internal.rawMouse) || _RGFW->rawMouse) { + return DefWindowProcW(hWnd, message, wParam, lParam); + } + + RGFW_mousePosCallback(win, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), _RGFW->vectorX, _RGFW->vectorY); + break; + } + case WM_INPUT: { + if (!(win->internal.rawMouse || _RGFW->rawMouse)) return DefWindowProcW(hWnd, message, wParam, lParam); + unsigned size = sizeof(RAWINPUT); + static RAWINPUT raw; + + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &size, sizeof(RAWINPUTHEADER)); + + if (raw.header.dwType != RIM_TYPEMOUSE || (raw.data.mouse.lLastX == 0 && raw.data.mouse.lLastY == 0) ) + break; + + float vecX = 0.0f; + float vecY = 0.0f; + + if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + POINT pos = {0, 0}; + int width, height; + + if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) { + pos.x += GetSystemMetrics(SM_XVIRTUALSCREEN); + pos.y += GetSystemMetrics(SM_YVIRTUALSCREEN); + width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + } + else { + width = GetSystemMetrics(SM_CXSCREEN); + height = GetSystemMetrics(SM_CYSCREEN); + } + + pos.x += (int) (((float)raw.data.mouse.lLastX / 65535.f) * (float)width); + pos.y += (int) (((float)raw.data.mouse.lLastY / 65535.f) * (float)height); + ScreenToClient(win->src.window, &pos); + + vecX = (float)(pos.x - win->internal.lastMouseX); + vecY = (float)(pos.y - win->internal.lastMouseY); + } else { + vecX = (float)(raw.data.mouse.lLastX); + vecY = (float)(raw.data.mouse.lLastY); + } + + RGFW_mousePosCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, vecX, vecY); + break; + } + case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: { + RGFW_mouseButton value = 0; + if (message == WM_XBUTTONDOWN) + value = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(wParam) == XBUTTON2); + else value = (message == WM_LBUTTONDOWN) ? (u8)RGFW_mouseLeft : + (message == WM_RBUTTONDOWN) ? (u8)RGFW_mouseRight : (u8)RGFW_mouseMiddle; + + RGFW_mouseButtonCallback(win, value, 1); + break; + } + case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: { + RGFW_mouseButton value = 0; + if (message == WM_XBUTTONUP) + value = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(wParam) == XBUTTON2); + else value = (message == WM_LBUTTONUP) ? (u8)RGFW_mouseLeft : + (message == WM_RBUTTONUP) ? (u8)RGFW_mouseRight : (u8)RGFW_mouseMiddle; + + RGFW_mouseButtonCallback(win, value, 0); + break; + } + case WM_MOUSEWHEEL: { + float scrollY = (float)((i16) HIWORD(wParam) / (double) WHEEL_DELTA); + RGFW_mouseScrollCallback(win, 0.0f, scrollY); + break; + } + case 0x020E: {/* WM_MOUSEHWHEEL */ + float scrollX = -(float)((i16) HIWORD(wParam) / (double) WHEEL_DELTA); + RGFW_mouseScrollCallback(win, scrollX, 0.0f); + break; + } + case WM_DROPFILES: { + HDROP drop = (HDROP) wParam; + POINT pt; + + /* Move the mouse to the position of the drop */ + DragQueryPoint(drop, &pt); + RGFW_dataDragCallback(win, pt.x, pt.y); + + if (!(win->internal.enabledEvents & RGFW_dataDrop)) return DefWindowProcW(hWnd, message, wParam, lParam); + char** files = _RGFW->files; + size_t count = DragQueryFileW(drop, 0xffffffff, NULL, 0); + + u32 i; + for (i = 0; i < count; i++) { + UINT length = DragQueryFileW(drop, i, NULL, 0); + if (length == 0) + continue; + + WCHAR buffer[RGFW_MAX_PATH * 2]; + if (length > (RGFW_MAX_PATH * 2) - 1) + length = RGFW_MAX_PATH * 2; + + DragQueryFileW(drop, i, buffer, length + 1); + + RGFW_createUTF8FromWideStringWin32(buffer, files[i], RGFW_MAX_PATH); + + files[i][RGFW_MAX_PATH - 1] = '\0'; + } + + DragFinish(drop); + + RGFW_dataDropCallback(win, files, count); + break; + } + default: break; + } + + return DefWindowProcW(hWnd, message, wParam, lParam); +} + +#ifndef RGFW_NO_DPI + HMODULE RGFW_Shcore_dll = NULL; + typedef HRESULT (WINAPI *PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,UINT*); + PFN_GetDpiForMonitor GetDpiForMonitorSRC = NULL; + #define GetDpiForMonitor GetDpiForMonitorSRC +#endif + +#if !defined(RGFW_NO_LOAD_WINMM) && !defined(RGFW_NO_WINMM) + HMODULE RGFW_winmm_dll = NULL; + typedef u32 (WINAPI * PFN_timeBeginPeriod)(u32); + typedef PFN_timeBeginPeriod PFN_timeEndPeriod; + PFN_timeBeginPeriod timeBeginPeriodSRC, timeEndPeriodSRC; + #define timeBeginPeriod timeBeginPeriodSRC + #define timeEndPeriod timeEndPeriodSRC +#elif !defined(RGFW_NO_WINMM) + __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); + __declspec(dllimport) u32 __stdcall timeEndPeriod(u32 uPeriod); +#endif +#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ + name##SRC = (PFN_##name)(RGFW_proc)GetProcAddress((proc), (#name)); \ + RGFW_ASSERT(name##SRC != NULL); \ + } + +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + + BITMAPV5HEADER bi; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = (i32)w; + bi.bV5Height = -((LONG) h); + bi.bV5Planes = 1; + bi.bV5BitCount = (format >= RGFW_formatRGBA8) ? 32 : 24; + bi.bV5Compression = BI_RGB; + + surface->native.bitmap = CreateDIBSection(_RGFW->root->src.hdc, + (BITMAPINFO*) &bi, DIB_RGB_COLORS, + (void**) &surface->native.bitmapBits, + NULL, (DWORD) 0); + + surface->native.format = (format >= RGFW_formatRGBA8) ? RGFW_formatBGRA8 : RGFW_formatBGR8; + + if (surface->native.bitmap == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create DIB section."); + return RGFW_FALSE; + } + + surface->native.hdcMem = CreateCompatibleDC(_RGFW->root->src.hdc); + SelectObject(surface->native.hdcMem, surface->native.bitmap); + + return RGFW_TRUE; +} + +void RGFW_surface_freePtr(RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + + DeleteDC(surface->native.hdcMem); + DeleteObject(surface->native.bitmap); +} + +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { + RGFW_copyImageData(surface->native.bitmapBits, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); + BitBlt(win->src.hdc, 0, 0, RGFW_MIN(win->w, surface->w), RGFW_MIN(win->h, surface->h), surface->native.hdcMem, 0, 0, SRCCOPY); +} + +void RGFW_window_setRawMouseModePlatform(RGFW_window* win, RGFW_bool state) { + RGFW_UNUSED(win); + RAWINPUTDEVICE id = { 0x01, 0x02, 0, win->src.window }; + id.dwFlags = (state == RGFW_TRUE) ? 0 : RIDEV_REMOVE; + + RegisterRawInputDevices(&id, 1, sizeof(id)); +} + +void RGFW_window_captureMousePlatform(RGFW_window* win, RGFW_bool state) { + if (state == RGFW_FALSE) { + ClipCursor(NULL); + return; + } + + RECT clipRect; + GetClientRect(win->src.window, &clipRect); + ClientToScreen(win->src.window, (POINT*) &clipRect.left); + ClientToScreen(win->src.window, (POINT*) &clipRect.right); + ClipCursor(&clipRect); +} + +#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) { x = LoadLibraryA(lib); RGFW_ASSERT(x != NULL); } + +#ifdef RGFW_DIRECTX +int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { + RGFW_ASSERT(win && pFactory && pDevice && swapchain); + + static DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 }; + swapChainDesc.BufferCount = 2; + swapChainDesc.BufferDesc.Width = win->w; + swapChainDesc.BufferDesc.Height = win->h; + swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.OutputWindow = (HWND)win->src.window; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.Windowed = TRUE; + swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + + HRESULT hr = pFactory->lpVtbl->CreateSwapChain(pFactory, (IUnknown*)pDevice, &swapChainDesc, swapchain); + if (FAILED(hr)) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, "Failed to create DirectX swap chain!"); + return -2; + } + + return 0; +} +#endif + +/* we're doing it with magic numbers because some keys are missing */ +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[0x00B] = RGFW_0; + _RGFW->keycodes[0x002] = RGFW_1; + _RGFW->keycodes[0x003] = RGFW_2; + _RGFW->keycodes[0x004] = RGFW_3; + _RGFW->keycodes[0x005] = RGFW_4; + _RGFW->keycodes[0x006] = RGFW_5; + _RGFW->keycodes[0x007] = RGFW_6; + _RGFW->keycodes[0x008] = RGFW_7; + _RGFW->keycodes[0x009] = RGFW_8; + _RGFW->keycodes[0x00A] = RGFW_9; + _RGFW->keycodes[0x01E] = RGFW_a; + _RGFW->keycodes[0x030] = RGFW_b; + _RGFW->keycodes[0x02E] = RGFW_c; + _RGFW->keycodes[0x020] = RGFW_d; + _RGFW->keycodes[0x012] = RGFW_e; + _RGFW->keycodes[0x021] = RGFW_f; + _RGFW->keycodes[0x022] = RGFW_g; + _RGFW->keycodes[0x023] = RGFW_h; + _RGFW->keycodes[0x017] = RGFW_i; + _RGFW->keycodes[0x024] = RGFW_j; + _RGFW->keycodes[0x025] = RGFW_k; + _RGFW->keycodes[0x026] = RGFW_l; + _RGFW->keycodes[0x032] = RGFW_m; + _RGFW->keycodes[0x031] = RGFW_n; + _RGFW->keycodes[0x018] = RGFW_o; + _RGFW->keycodes[0x019] = RGFW_p; + _RGFW->keycodes[0x010] = RGFW_q; + _RGFW->keycodes[0x013] = RGFW_r; + _RGFW->keycodes[0x01F] = RGFW_s; + _RGFW->keycodes[0x014] = RGFW_t; + _RGFW->keycodes[0x016] = RGFW_u; + _RGFW->keycodes[0x02F] = RGFW_v; + _RGFW->keycodes[0x011] = RGFW_w; + _RGFW->keycodes[0x02D] = RGFW_x; + _RGFW->keycodes[0x015] = RGFW_y; + _RGFW->keycodes[0x02C] = RGFW_z; + _RGFW->keycodes[0x028] = RGFW_apostrophe; + _RGFW->keycodes[0x02B] = RGFW_backSlash; + _RGFW->keycodes[0x033] = RGFW_comma; + _RGFW->keycodes[0x00D] = RGFW_equals; + _RGFW->keycodes[0x029] = RGFW_backtick; + _RGFW->keycodes[0x01A] = RGFW_bracket; + _RGFW->keycodes[0x00C] = RGFW_minus; + _RGFW->keycodes[0x034] = RGFW_period; + _RGFW->keycodes[0x01B] = RGFW_closeBracket; + _RGFW->keycodes[0x027] = RGFW_semicolon; + _RGFW->keycodes[0x035] = RGFW_slash; + _RGFW->keycodes[0x056] = RGFW_world2; + _RGFW->keycodes[0x00E] = RGFW_backSpace; + _RGFW->keycodes[0x153] = RGFW_delete; + _RGFW->keycodes[0x14F] = RGFW_end; + _RGFW->keycodes[0x01C] = RGFW_enter; + _RGFW->keycodes[0x001] = RGFW_escape; + _RGFW->keycodes[0x147] = RGFW_home; + _RGFW->keycodes[0x152] = RGFW_insert; + _RGFW->keycodes[0x15D] = RGFW_menu; + _RGFW->keycodes[0x151] = RGFW_pageDown; + _RGFW->keycodes[0x149] = RGFW_pageUp; + _RGFW->keycodes[0x045] = RGFW_pause; + _RGFW->keycodes[0x039] = RGFW_space; + _RGFW->keycodes[0x00F] = RGFW_tab; + _RGFW->keycodes[0x03A] = RGFW_capsLock; + _RGFW->keycodes[0x145] = RGFW_numLock; + _RGFW->keycodes[0x046] = RGFW_scrollLock; + _RGFW->keycodes[0x03B] = RGFW_F1; + _RGFW->keycodes[0x03C] = RGFW_F2; + _RGFW->keycodes[0x03D] = RGFW_F3; + _RGFW->keycodes[0x03E] = RGFW_F4; + _RGFW->keycodes[0x03F] = RGFW_F5; + _RGFW->keycodes[0x040] = RGFW_F6; + _RGFW->keycodes[0x041] = RGFW_F7; + _RGFW->keycodes[0x042] = RGFW_F8; + _RGFW->keycodes[0x043] = RGFW_F9; + _RGFW->keycodes[0x044] = RGFW_F10; + _RGFW->keycodes[0x057] = RGFW_F11; + _RGFW->keycodes[0x058] = RGFW_F12; + _RGFW->keycodes[0x064] = RGFW_F13; + _RGFW->keycodes[0x065] = RGFW_F14; + _RGFW->keycodes[0x066] = RGFW_F15; + _RGFW->keycodes[0x067] = RGFW_F16; + _RGFW->keycodes[0x068] = RGFW_F17; + _RGFW->keycodes[0x069] = RGFW_F18; + _RGFW->keycodes[0x06A] = RGFW_F19; + _RGFW->keycodes[0x06B] = RGFW_F20; + _RGFW->keycodes[0x06C] = RGFW_F21; + _RGFW->keycodes[0x06D] = RGFW_F22; + _RGFW->keycodes[0x06E] = RGFW_F23; + _RGFW->keycodes[0x076] = RGFW_F24; + _RGFW->keycodes[0x038] = RGFW_altL; + _RGFW->keycodes[0x01D] = RGFW_controlL; + _RGFW->keycodes[0x02A] = RGFW_shiftL; + _RGFW->keycodes[0x15B] = RGFW_superL; + _RGFW->keycodes[0x137] = RGFW_printScreen; + _RGFW->keycodes[0x138] = RGFW_altR; + _RGFW->keycodes[0x11D] = RGFW_controlR; + _RGFW->keycodes[0x036] = RGFW_shiftR; + _RGFW->keycodes[0x15C] = RGFW_superR; + _RGFW->keycodes[0x150] = RGFW_down; + _RGFW->keycodes[0x14B] = RGFW_left; + _RGFW->keycodes[0x14D] = RGFW_right; + _RGFW->keycodes[0x148] = RGFW_up; + _RGFW->keycodes[0x052] = RGFW_kp0; + _RGFW->keycodes[0x04F] = RGFW_kp1; + _RGFW->keycodes[0x050] = RGFW_kp2; + _RGFW->keycodes[0x051] = RGFW_kp3; + _RGFW->keycodes[0x04B] = RGFW_kp4; + _RGFW->keycodes[0x04C] = RGFW_kp5; + _RGFW->keycodes[0x04D] = RGFW_kp6; + _RGFW->keycodes[0x047] = RGFW_kp7; + _RGFW->keycodes[0x048] = RGFW_kp8; + _RGFW->keycodes[0x049] = RGFW_kp9; + _RGFW->keycodes[0x04E] = RGFW_kpPlus; + _RGFW->keycodes[0x053] = RGFW_kpPeriod; + _RGFW->keycodes[0x135] = RGFW_kpSlash; + _RGFW->keycodes[0x11C] = RGFW_kpReturn; + _RGFW->keycodes[0x059] = RGFW_kpEqual; + _RGFW->keycodes[0x037] = RGFW_kpMultiply; + _RGFW->keycodes[0x04A] = RGFW_kpMinus; +} + + +i32 RGFW_initPlatform(void) { +#ifndef RGFW_NO_DPI + #if (_WIN32_WINNT >= 0x0600) + SetProcessDPIAware(); + #endif +#endif + + #ifndef RGFW_NO_WINMM + #ifndef RGFW_NO_LOAD_WINMM + RGFW_LOAD_LIBRARY(RGFW_winmm_dll, "winmm.dll"); + RGFW_PROC_DEF(RGFW_winmm_dll, timeBeginPeriod); + RGFW_PROC_DEF(RGFW_winmm_dll, timeEndPeriod); + #endif + timeBeginPeriod(1); + #endif + + #ifndef RGFW_NO_DWM + RGFW_LOAD_LIBRARY(RGFW_dwm_dll, "dwmapi.dll"); + RGFW_PROC_DEF(RGFW_dwm_dll, DwmEnableBlurBehindWindow); + RGFW_PROC_DEF(RGFW_dwm_dll, DwmSetWindowAttribute); + #endif + + RGFW_LOAD_LIBRARY(RGFW_wgl_dll, "opengl32.dll"); + #ifndef RGFW_NO_LOAD_WGL + RGFW_PROC_DEF(RGFW_wgl_dll, wglCreateContext); + RGFW_PROC_DEF(RGFW_wgl_dll, wglDeleteContext); + RGFW_PROC_DEF(RGFW_wgl_dll, wglGetProcAddress); + RGFW_PROC_DEF(RGFW_wgl_dll, wglMakeCurrent); + RGFW_PROC_DEF(RGFW_wgl_dll, wglGetCurrentDC); + RGFW_PROC_DEF(RGFW_wgl_dll, wglGetCurrentContext); + RGFW_PROC_DEF(RGFW_wgl_dll, wglShareLists); + #endif + + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); + return 1; +} + +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { + if (name[0] == 0) name = (char*) " "; + win->src.hIconSmall = win->src.hIconBig = NULL; + win->src.maxSizeW = 0; + win->src.maxSizeH = 0; + win->src.minSizeW = 0; + win->src.minSizeH = 0; + win->src.aspectRatioW = 0; + win->src.aspectRatioH = 0; + + HINSTANCE inh = GetModuleHandleA(NULL); + + #ifndef __cplusplus + WNDCLASSW Class = {0}; /*!< Setup the Window class. */ + #else + WNDCLASSW Class = {}; + #endif + + if (_RGFW->className == NULL) + _RGFW->className = (char*)name; + + wchar_t wide_class[256]; + MultiByteToWideChar(CP_UTF8, 0, _RGFW->className, -1, wide_class, 255); + + Class.lpszClassName = wide_class; + Class.hInstance = inh; + Class.hCursor = LoadCursor(NULL, IDC_ARROW); + Class.lpfnWndProc = WndProcW; + Class.cbClsExtra = sizeof(RGFW_window*); + + Class.hIcon = (HICON)LoadImageA(GetModuleHandleW(NULL), "RGFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + if (Class.hIcon == NULL) + Class.hIcon = (HICON)LoadImageA(NULL, (LPCSTR)IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + + RegisterClassW(&Class); + + DWORD window_style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + + RECT windowRect, clientRect; + + if (!(flags & RGFW_windowNoBorder)) { + window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX; + + if (!(flags & RGFW_windowNoResize)) + window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX; + } else + window_style |= WS_POPUP | WS_VISIBLE | WS_SYSMENU; + + wchar_t wide_name[256]; + MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 255); + HWND dummyWin = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->x, win->y, win->w, win->h, 0, 0, inh, 0); + + GetWindowRect(dummyWin, &windowRect); + GetClientRect(dummyWin, &clientRect); + +#ifdef RGFW_OPENGL + RGFW_win32_loadOpenGLFuncs(dummyWin); +#endif + + DestroyWindow(dummyWin); + + win->src.offsetW = (i32)(windowRect.right - windowRect.left) - (i32)(clientRect.right - clientRect.left); + win->src.offsetH = (i32)(windowRect.bottom - windowRect.top) - (i32)(clientRect.bottom - clientRect.top); + win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->x, win->y, win->w + (i32)win->src.offsetW, win->h + (i32)win->src.offsetH, 0, 0, inh, 0); + SetPropW(win->src.window, L"RGFW", win); + RGFW_window_resize(win, win->w, win->h); /* so WM_GETMINMAXINFO gets called again */ + + if (flags & RGFW_windowAllowDND) { + win->internal.flags |= RGFW_windowAllowDND; + RGFW_window_setDND(win, 1); + } + win->src.hdc = GetDC(win->src.window); + + RGFW_win32_makeWindowDarkMode(win, RGFW_win32_getDarkModeState()); + RGFW_win32_makeWindowTransparent(win); + return win; +} + +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + LONG style = GetWindowLong(win->src.window, GWL_STYLE); + + if (border == 0) { + SetWindowLong(win->src.window, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos( + win->src.window, HWND_TOP, 0, 0, 0, 0, + SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE + ); + } + else { + if (win->internal.flags & RGFW_windowNoResize) style &= ~WS_MAXIMIZEBOX; + SetWindowLong(win->src.window, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + SetWindowPos( + win->src.window, HWND_TOP, 0, 0, 0, 0, + SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE + ); + } +} + +void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { + RGFW_setBit(&win->internal.flags, RGFW_windowAllowDND, allow); + DragAcceptFiles(win->src.window, allow); +} + +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { + POINT p; + GetCursorPos(&p); + if (x) *x = p.x; + if (y) *y = p.y; + return RGFW_TRUE; +} + +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + win->src.aspectRatioW = w; + win->src.aspectRatioH = h; +} + +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + win->src.minSizeW = w; + win->src.minSizeH = h; +} + +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + win->src.maxSizeW = w; + win->src.maxSizeH = h; +} + +void RGFW_window_focus(RGFW_window* win) { + RGFW_ASSERT(win); + SetForegroundWindow(win->src.window); + SetFocus(win->src.window); +} + +void RGFW_window_raise(RGFW_window* win) { + RGFW_ASSERT(win); + BringWindowToTop(win->src.window); + SetWindowPos(win->src.window, HWND_TOP, win->x, win->y, win->w, win->h, SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + + if (fullscreen == RGFW_FALSE) { + RGFW_window_setBorder(win, 1); + SetWindowPos(win->src.window, HWND_NOTOPMOST, win->internal.oldX, win->internal.oldY, win->internal.oldW + (i32)win->src.offsetW, win->internal.oldH + (i32)win->src.offsetH, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + + win->internal.flags &= ~(u32)RGFW_windowFullscreen; + win->x = win->internal.oldX; + win->y = win->internal.oldY; + win->w = win->internal.oldW; + win->h = win->internal.oldH; + return; + } + + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + win->internal.flags |= RGFW_windowFullscreen; + + RGFW_monitor* mon = RGFW_window_getMonitor(win); + + RGFW_window_setBorder(win, 0); + RGFW_monitor_scaleToWindow(mon, win); + + SetWindowPos(win->src.window, HWND_TOPMOST, (i32)mon->x, (i32)mon->y, (i32)mon->mode.w, (i32)mon->mode.h, SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); + + win->x = mon->x; + win->y = mon->y; + win->w = mon->mode.w; + win->h = mon->mode.h; +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_window_hide(win); + ShowWindow(win->src.window, SW_MAXIMIZE); +} + +void RGFW_window_minimize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + ShowWindow(win->src.window, SW_MINIMIZE); +} + +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + if (floating) SetWindowPos(win->src.window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + else SetWindowPos(win->src.window, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + SetWindowLong(win->src.window, GWL_EXSTYLE, WS_EX_LAYERED); + SetLayeredWindowAttributes(win->src.window, 0, opacity, LWA_ALPHA); +} + +void RGFW_window_restore(RGFW_window* win) { RGFW_window_show(win); } + +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { + return (GetWindowLongPtr(win->src.window, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0; +} + +void RGFW_stopCheckEvents(void) { + PostMessageW(_RGFW->root->src.window, WM_NULL, 0, 0); +} + +void RGFW_waitForEvent(i32 waitMS) { + MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD)waitMS, QS_ALLINPUT); +} + +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + UINT vsc = RGFW_rgfwToApiKey(rgfw_keycode); /* Should return a Windows VK_* code */ + BYTE keyboardState[256] = {0}; + + if (!GetKeyboardState(keyboardState)) + return (u8)rgfw_keycode; + + UINT vk = MapVirtualKeyW(vsc, MAPVK_VSC_TO_VK); + HKL layout = GetKeyboardLayout(0); + + wchar_t charBuffer[2] = {0}; + int result = ToUnicodeEx(vk, vsc, keyboardState, charBuffer, 1, 0, layout); + + if (result <= 0) + return (u8)rgfw_keycode; + + return (u8)charBuffer[0]; +} + +void RGFW_pollEvents(void) { + RGFW_resetPrevState(); + MSG msg; + while (PeekMessageA(&msg, NULL, 0u, 0u, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } +} + +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return IsWindowVisible(win->src.window) == 0 && !RGFW_window_isMinimized(win); +} + +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + #ifndef __cplusplus + WINDOWPLACEMENT placement = {0}; + #else + WINDOWPLACEMENT placement = {}; + #endif + GetWindowPlacement(win->src.window, &placement); + return placement.showCmd == SW_SHOWMINIMIZED; +} + +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + #ifndef __cplusplus + WINDOWPLACEMENT placement = {0}; + #else + WINDOWPLACEMENT placement = {}; + #endif + GetWindowPlacement(win->src.window, &placement); + return placement.showCmd == SW_SHOWMAXIMIZED || IsZoomed(win->src.window); +} + +RGFW_bool RGFW_monitor_getWorkarea(RGFW_monitor* monitor, i32* x, i32* y, i32* width, i32* height) { + MONITORINFOEX mi; + mi.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfoA(monitor->node->hMonitor, (LPMONITORINFO)&mi); + + if (x) *x = mi.rcWork.left; + if (y) *y = mi.rcWork.top; + if (width) *width = mi.rcWork.right - mi.rcWork.left; + if (height) *height = mi.rcWork.bottom - mi.rcWork.top; + + return RGFW_TRUE; +} + +size_t RGFW_monitor_getGammaRampPtr(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + WORD values[3][256]; + + HDC dc = CreateDCW(L"DISPLAY", monitor->node->adapterName, NULL, NULL); + GetDeviceGammaRamp(dc, values); + DeleteDC(dc); + + if (ramp) { + memcpy(ramp->red, values[0], sizeof(values[0])); + memcpy(ramp->green, values[1], sizeof(values[1])); + memcpy(ramp->blue, values[2], sizeof(values[2])); + } + + return sizeof(values[0]) / sizeof(WORD); +} + +RGFW_bool RGFW_monitor_setGammaRamp(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + WORD values[3][256]; + if (ramp->count != 256) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errX11, "Win32: Gamma ramp size must be 256"); + return RGFW_FALSE; + } + + memcpy(values[0], ramp->red, sizeof(values[0])); + memcpy(values[1], ramp->green, sizeof(values[1])); + memcpy(values[2], ramp->blue, sizeof(values[2])); + + HDC dc = CreateDCW(L"DISPLAY", monitor->node->adapterName, NULL, NULL); + SetDeviceGammaRamp(dc, values); + DeleteDC(dc); + return RGFW_TRUE; +} + +BOOL CALLBACK RGFW_win32_getMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData); +BOOL CALLBACK RGFW_win32_getMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + RGFW_UNUSED(hMonitor); + RGFW_UNUSED(hdcMonitor); + RGFW_UNUSED(lprcMonitor); + RGFW_UNUSED(dwData); + + MONITORINFOEXW mi; + ZeroMemory(&mi, sizeof(mi)); + mi.cbSize = sizeof(mi); + + if (GetMonitorInfoW(hMonitor, (MONITORINFO*) &mi)) { + RGFW_monitorNode* node = (RGFW_monitorNode*)dwData; + if (wcscmp(mi.szDevice, node->adapterName) == 0) { + node->hMonitor = hMonitor; + } + } + + return TRUE; +} + +RGFWDEF void RGFW_win32_getMode(DEVMODEW* dm, RGFW_monitorMode* mode); +void RGFW_win32_getMode(DEVMODEW* dm, RGFW_monitorMode* mode) { + mode->w = (i32)dm->dmPelsWidth; + mode->h = (i32)dm->dmPelsHeight; + RGFW_splitBPP(dm->dmBitsPerPel, mode); + + switch (dm->dmDisplayFrequency) { + case 119: + case 59: + case 29: + mode->refreshRate = ((float)(dm->dmDisplayFrequency + 1) * 1000.0f) / 1001.f; + break; + default: + mode->refreshRate = (float)dm->dmDisplayFrequency; + break; + } +} + +size_t RGFW_monitor_getModesPtr(RGFW_monitor* monitor, RGFW_monitorMode** modes){ + size_t count = 0; + DWORD modeIndex = 0; + + for (;;) { + DEVMODEW dm; + ZeroMemory(&dm, sizeof(dm)); + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitor->node->adapterName, modeIndex, &dm)) + break; + + modeIndex++; + + if (dm.dmBitsPerPel < 15) + continue; + + if (modes) { + RGFW_monitorMode mode; + RGFW_win32_getMode(&dm, &mode); + + size_t i; + for (i = 0; i < count; i++) { + if (RGFW_monitorModeCompare(&(*modes)[i], &mode, RGFW_monitorAll) == RGFW_TRUE) { + break; + } + } + + if (i < count) { + continue; + } + + (*modes)[count] = mode; + } + + count += 1; + } + + return count; +} + +RGFWDEF void RGFW_win32_createMonitor(DISPLAY_DEVICEW* adapter, DISPLAY_DEVICEW* dd); +void RGFW_win32_createMonitor(DISPLAY_DEVICEW* adapter, DISPLAY_DEVICEW* dd) { + DEVMODEW dm; + ZeroMemory(&dm, sizeof(dm)); + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { + return; + } + + RGFW_monitorNode* node = RGFW_monitors_add(NULL); + + wcscpy(node->adapterName, adapter->DeviceName); + wcscpy(node->deviceName, dd->DeviceName); + + RGFW_createUTF8FromWideStringWin32(dd->DeviceString, node->mon.name, sizeof(node->mon.name)); + node->mon.name[sizeof(node->mon.name) - 1] = '\0'; + + RECT rect; + rect.left = (LONG)dm.dmPosition.x; + rect.top = (LONG)dm.dmPosition.y; + rect.right = (LONG)((LONG)dm.dmPosition.x + (LONG)dm.dmPelsWidth); + rect.bottom = (LONG)((long)dm.dmPosition.y + (LONG)dm.dmPelsHeight); + EnumDisplayMonitors(NULL, &rect, RGFW_win32_getMonitorHandle, (LPARAM)node); + + RGFW_win32_getMode(&dm, &node->mon.mode); + + MONITORINFOEXW monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFOEXW); + GetMonitorInfoW(node->hMonitor, (LPMONITORINFO)&monitorInfo); + + node->mon.x = monitorInfo.rcMonitor.left; + node->mon.y = monitorInfo.rcMonitor.top; + + HDC hdc = CreateDCW(monitorInfo.szDevice, NULL, NULL, NULL); + /* get pixels per inch */ + float dpiX = (float)GetDeviceCaps(hdc, LOGPIXELSX); + float dpiY = (float)GetDeviceCaps(hdc, LOGPIXELSX); + + node->mon.scaleX = dpiX / 96.0f; + node->mon.scaleY = dpiY / 96.0f; + node->mon.pixelRatio = dpiX >= 192.0f ? 2.0f : 1.0f; + + node->mon.physW = (float)GetDeviceCaps(hdc, HORZSIZE) / 25.4f; + node->mon.physH = (float)GetDeviceCaps(hdc, VERTSIZE) / 25.4f; + DeleteDC(hdc); + +#ifndef RGFW_NO_DPI + RGFW_LOAD_LIBRARY(RGFW_Shcore_dll, "shcore.dll"); + RGFW_PROC_DEF(RGFW_Shcore_dll, GetDpiForMonitor); + + if (GetDpiForMonitor != NULL) { + u32 x, y; + GetDpiForMonitor(node->hMonitor, MDT_EFFECTIVE_DPI, &x, &y); + node->mon.scaleX = (float) (x) / (float) 96.0f; + node->mon.scaleY = (float) (y) / (float) 96.0f; + node->mon.pixelRatio = dpiX >= 192.0f ? 2.0f : 1.0f; + } +#endif + + if (monitorInfo.dwFlags & MONITORINFOF_PRIMARY) { + _RGFW->monitors.primary = node; + } + + RGFW_monitorCallback(_RGFW->root, &node->mon, RGFW_TRUE); +} + +void RGFW_pollMonitors(void) { + for (RGFW_monitorNode* node = _RGFW->monitors.list.head; node; node = node->next) { + node->disconnected = RGFW_TRUE; + } + + /* loop through display adapters (GPU) */ + DISPLAY_DEVICEW adapter; + DWORD adapterNum; + for (adapterNum = 0; ; adapterNum++) { + ZeroMemory(&adapter, sizeof(adapter)); + adapter.cb = sizeof(adapter); + + if (!EnumDisplayDevicesW(NULL, adapterNum, &adapter, 0)) + break; + + if (!(adapter.StateFlags & DISPLAY_DEVICE_ACTIVE)) + continue; + + DISPLAY_DEVICEW dd; + dd.cb = sizeof(dd); + + /* loop through display devices (monitors) */ + DWORD deviceNum; + for (deviceNum = 0; ; deviceNum++) { + ZeroMemory(&dd, sizeof(dd)); + dd.cb = sizeof(dd); + + if (!EnumDisplayDevicesW(adapter.DeviceName, deviceNum, &dd, 0)) + break; + + if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) + continue; + + RGFW_monitorNode* node; + for (node = _RGFW->monitors.list.head; node; node = node->next) { + if (node->disconnected == RGFW_TRUE && wcscmp(node->deviceName, dd.DeviceName) == 0) { + node->disconnected = RGFW_FALSE; + EnumDisplayMonitors(NULL, NULL, RGFW_win32_getMonitorHandle, (LPARAM) &node->mon); + break; + } + } + + if (node) { + continue; + } + + RGFW_win32_createMonitor(&adapter, &dd); + } + + /* if there are no display devices, just use the monitor directly (hack borrowed from GLFW (I'm not giving it back)) */ + if (deviceNum == 0) { + RGFW_monitorNode* node; + for (node = _RGFW->monitors.list.head; node; node = node->next) { + if (node->disconnected == RGFW_TRUE && wcscmp(node->adapterName, adapter.DeviceName) == 0) { + node->disconnected = RGFW_FALSE; + break; + } + } + + if (node) { + continue; + } + + RGFW_win32_createMonitor(&adapter, NULL); + } + } + + RGFW_monitors_refresh(); +} + +RGFW_monitor* RGFW_window_getMonitor(RGFW_window* win) { + HMONITOR src = MonitorFromWindow(win->src.window, MONITOR_DEFAULTTOPRIMARY); + RGFW_monitorNode* node = _RGFW->monitors.list.head; + + for (node = _RGFW->monitors.list.head; node; node = node->next) { + if (node->hMonitor == src) { + return &node->mon; + } + } + + return RGFW_getPrimaryMonitor(); +} + +RGFW_bool RGFW_monitor_setMode(RGFW_monitor* mon, RGFW_monitorMode* mode) { + DEVMODEW dm; + ZeroMemory(&dm, sizeof(dm)); + dm.dmSize = sizeof(dm); + + dm.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT; + dm.dmPelsWidth = (u32)mode->w; + dm.dmPelsHeight = (u32)mode->h; + + dm.dmFields |= DM_DISPLAYFREQUENCY; + dm.dmDisplayFrequency = (DWORD)mode->refreshRate; + + dm.dmFields |= DM_BITSPERPEL; + dm.dmBitsPerPel = (DWORD)(mode->red + mode->green + mode->blue); + + if (ChangeDisplaySettingsExW(mon->node->adapterName, &dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { + if (ChangeDisplaySettingsExW(mon->node->adapterName, &dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) + return RGFW_TRUE; + return RGFW_FALSE; + } else return RGFW_FALSE; +} + +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request) { + HMONITOR src = mon->node->hMonitor; + + MONITORINFOEX monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); + + DEVMODEW dm; + ZeroMemory(&dm, sizeof(dm)); + dm.dmSize = sizeof(dm); + + if (EnumDisplaySettingsW(mon->node->adapterName, ENUM_CURRENT_SETTINGS, &dm)) { + if (request & RGFW_monitorScale) { + dm.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT; + dm.dmPelsWidth = (u32)mode->w; + dm.dmPelsHeight = (u32)mode->h; + } + + if (request & RGFW_monitorRefresh) { + dm.dmFields |= DM_DISPLAYFREQUENCY; + dm.dmDisplayFrequency = (DWORD)mode->refreshRate; + } + + if (request & RGFW_monitorRGB) { + dm.dmFields |= DM_BITSPERPEL; + dm.dmBitsPerPel = (DWORD)(mode->red + mode->green + mode->blue); + } + + if (ChangeDisplaySettingsExW(mon->node->adapterName, &dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { + if (ChangeDisplaySettingsExW(mon->node->adapterName, &dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) { + RGFW_win32_getMode(&dm, &mon->mode); + return RGFW_TRUE; + } + return RGFW_FALSE; + } else return RGFW_FALSE; + } + + return RGFW_FALSE; +} + +HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon); +HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon) { + BITMAPV5HEADER bi; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = (i32)w; + bi.bV5Height = -((LONG) h); + bi.bV5Planes = 1; + bi.bV5BitCount = (WORD)32; + bi.bV5Compression = BI_RGB; + HDC dc = GetDC(NULL); + u8* target = NULL; + + HBITMAP color = CreateDIBSection(dc, + (BITMAPINFO*) &bi, DIB_RGB_COLORS, (void**) &target, + NULL, (DWORD) 0); + + RGFW_copyImageData(target, w, h, RGFW_formatBGRA8, data, format); + ReleaseDC(NULL, dc); + + HBITMAP mask = CreateBitmap((i32)w, (i32)h, 1, 1, NULL); + + ICONINFO ii; + ZeroMemory(&ii, sizeof(ii)); + ii.fIcon = icon; + ii.xHotspot = (u32)w / 2; + ii.yHotspot = (u32)h / 2; + ii.hbmMask = mask; + ii.hbmColor = color; + + HICON handle = CreateIconIndirect(&ii); + + DeleteObject(color); + DeleteObject(mask); + + return handle; +} +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { + HCURSOR cursor = (HCURSOR) RGFW_loadHandleImage(data, w, h, format, FALSE); + return cursor; +} + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { + RGFW_ASSERT(win && mouse); + SetClassLongPtrA(win->src.window, GCLP_HCURSOR, (LPARAM) mouse); + SetCursor((HCURSOR)mouse); +} + +void RGFW_freeMouse(RGFW_mouse* mouse) { + RGFW_ASSERT(mouse); + DestroyCursor((HCURSOR)mouse); +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + RGFW_ASSERT(win != NULL); + + u32 mouseIcon = 0; + + switch (mouse) { + case RGFW_mouseNormal: mouseIcon = OCR_NORMAL; break; + case RGFW_mouseArrow: mouseIcon = OCR_NORMAL; break; + case RGFW_mouseIbeam: mouseIcon = OCR_IBEAM; break; + case RGFW_mouseWait: mouseIcon = OCR_WAIT; break; + case RGFW_mouseCrosshair: mouseIcon = OCR_CROSS; break; + case RGFW_mouseProgress: mouseIcon = OCR_APPSTARTING; break; + case RGFW_mouseResizeNWSE: mouseIcon = OCR_SIZENWSE; break; + case RGFW_mouseResizeNESW: mouseIcon = OCR_SIZENESW; break; + case RGFW_mouseResizeEW: mouseIcon = OCR_SIZEWE; break; + case RGFW_mouseResizeNS: mouseIcon = OCR_SIZENS; break; + case RGFW_mouseResizeAll: mouseIcon = OCR_SIZEALL; break; + case RGFW_mouseNotAllowed: mouseIcon = OCR_NO; break; + case RGFW_mousePointingHand: mouseIcon = OCR_HAND; break; + case RGFW_mouseResizeNW: mouseIcon = OCR_SIZENWSE; break; + case RGFW_mouseResizeN: mouseIcon = OCR_SIZENS; break; + case RGFW_mouseResizeNE: mouseIcon = OCR_SIZENESW; break; + case RGFW_mouseResizeE: mouseIcon = OCR_SIZEWE; break; + case RGFW_mouseResizeSE: mouseIcon = OCR_SIZENWSE; break; + case RGFW_mouseResizeS: mouseIcon = OCR_SIZENS; break; + case RGFW_mouseResizeSW: mouseIcon = OCR_SIZENESW; break; + case RGFW_mouseResizeW: mouseIcon = OCR_SIZEWE; break; + default: return RGFW_FALSE; + } + + char* icon = MAKEINTRESOURCEA(mouseIcon); + + SetClassLongPtrA(win->src.window, GCLP_HCURSOR, (LPARAM) LoadCursorA(NULL, icon)); + SetCursor(LoadCursorA(NULL, icon)); + return RGFW_TRUE; +} + +void RGFW_window_hide(RGFW_window* win) { + ShowWindow(win->src.window, SW_HIDE); +} + +void RGFW_window_show(RGFW_window* win) { + if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + ShowWindow(win->src.window, SW_RESTORE); +} + +void RGFW_window_flash(RGFW_window* win, RGFW_flashRequest request) { + if (RGFW_window_isInFocus(win) && request) { + return; + } + + FLASHWINFO desc; + RGFW_MEMSET(&desc, 0, sizeof(desc)); + + desc.cbSize = sizeof(desc); + desc.hwnd = win->src.window; + + switch (request) { + case RGFW_flashCancel: + desc.dwFlags = FLASHW_STOP; + break; + case RGFW_flashBriefly: + desc.dwFlags = FLASHW_TRAY; + desc.uCount = 1; + break; + case RGFW_flashUntilFocused: + desc.dwFlags = (FLASHW_TRAY | FLASHW_TIMERNOFG); + break; + default: break; + } + + FlashWindowEx(&desc); +} + +#define RGFW_FREE_LIBRARY(x) if (x != NULL) FreeLibrary(x); x = NULL; +void RGFW_deinitPlatform(void) { + #ifndef RGFW_NO_DPI + RGFW_FREE_LIBRARY(RGFW_Shcore_dll); + #endif + + #ifndef RGFW_NO_WINMM + timeEndPeriod(1); + #ifndef RGFW_NO_LOAD_WINMM + RGFW_FREE_LIBRARY(RGFW_winmm_dll); + #endif + #endif + + RGFW_FREE_LIBRARY(RGFW_wgl_dll); + + RGFW_freeMouse(_RGFW->hiddenMouse); +} + + +void RGFW_window_closePlatform(RGFW_window* win) { + RemovePropW(win->src.window, L"RGFW"); + ReleaseDC(win->src.window, win->src.hdc); /*!< delete device context */ + DestroyWindow(win->src.window); /*!< delete window */ + + if (win->src.hIconSmall) DestroyIcon(win->src.hIconSmall); + if (win->src.hIconBig) DestroyIcon(win->src.hIconBig); +} + +void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + + win->x = x; + win->y = y; + SetWindowPos(win->src.window, HWND_TOP, win->x, win->y, 0, 0, SWP_NOSIZE); +} + +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + + win->w = w; + win->h = h; + SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->w + (i32)win->src.offsetW, win->h + (i32)win->src.offsetH, SWP_NOMOVE); +} + + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + if (name == NULL) name = "\0"; + + wchar_t wide_name[256]; + MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 256); + SetWindowTextW(win->src.window, wide_name); +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { + RGFW_ASSERT(win != NULL); + COLORREF key = 0; + BYTE alpha = 0; + DWORD flags = 0; + i32 exStyle = GetWindowLongW(win->src.window, GWL_EXSTYLE); + + if (exStyle & WS_EX_LAYERED) + GetLayeredWindowAttributes(win->src.window, &key, &alpha, &flags); + + if (passthrough) + exStyle |= (WS_EX_TRANSPARENT | WS_EX_LAYERED); + else { + exStyle &= ~WS_EX_TRANSPARENT; + if (exStyle & WS_EX_LAYERED && !(flags & LWA_ALPHA)) + exStyle &= ~WS_EX_LAYERED; + } + + SetWindowLongW(win->src.window, GWL_EXSTYLE, exStyle); + + if (passthrough) + SetLayeredWindowAttributes(win->src.window, key, alpha, flags); +} +#endif + +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { + RGFW_ASSERT(win != NULL); + #ifndef RGFW_WIN95 + if (win->src.hIconSmall && (type & RGFW_iconWindow)) DestroyIcon(win->src.hIconSmall); + if (win->src.hIconBig && (type & RGFW_iconTaskbar)) DestroyIcon(win->src.hIconBig); + + if (data == NULL) { + HICON defaultIcon = LoadIcon(NULL, IDI_APPLICATION); + if (type & RGFW_iconWindow) + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)defaultIcon); + if (type & RGFW_iconTaskbar) + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)defaultIcon); + return RGFW_TRUE; + } + + if (type & RGFW_iconWindow) { + win->src.hIconSmall = RGFW_loadHandleImage(data, w, h, format, TRUE); + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)win->src.hIconSmall); + } + if (type & RGFW_iconTaskbar) { + win->src.hIconBig = RGFW_loadHandleImage(data, w, h, format, TRUE); + SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)win->src.hIconBig); + } + return RGFW_TRUE; + #else + RGFW_UNUSED(img); + RGFW_UNUSED(type); + return RGFW_FALSE; + #endif +} + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + /* Open the clipboard */ + if (OpenClipboard(NULL) == 0) + return -1; + + /* Get the clipboard data as a Unicode string */ + HANDLE hData = GetClipboardData(CF_UNICODETEXT); + if (hData == NULL) { + CloseClipboard(); + return -1; + } + + wchar_t* wstr = (wchar_t*) GlobalLock(hData); + + RGFW_ssize_t textLen = 0; + + { + setlocale(LC_ALL, "en_US.UTF-8"); + + textLen = (RGFW_ssize_t)wcstombs(NULL, wstr, 0) + 1; + if (str != NULL && (RGFW_ssize_t)strCapacity <= textLen - 1) + textLen = 0; + + if (str != NULL && textLen) { + if (textLen > 1) + wcstombs(str, wstr, (size_t)(textLen)); + + str[textLen - 1] = '\0'; + } + } + + /* Release the clipboard data */ + GlobalUnlock(hData); + CloseClipboard(); + + return textLen; +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + HANDLE object; + WCHAR* buffer; + + object = GlobalAlloc(GMEM_MOVEABLE, (1 + textLen) * sizeof(WCHAR)); + if (!object) + return; + + buffer = (WCHAR*) GlobalLock(object); + if (!buffer) { + GlobalFree(object); + return; + } + + MultiByteToWideChar(CP_UTF8, 0, text, -1, buffer, (i32)textLen); + GlobalUnlock(object); + + if (!OpenClipboard(_RGFW->root->src.window)) { + GlobalFree(object); + return; + } + + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, object); + CloseClipboard(); +} + +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + win->internal.lastMouseX = x - win->x; + win->internal.lastMouseY = y - win->y; + SetCursorPos(x, y); +} + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char * extension, size_t len) { + const char* extensions = NULL; + + RGFW_proc proc = RGFW_getProcAddress_OpenGL("wglGetExtensionsStringARB"); + RGFW_proc proc2 = RGFW_getProcAddress_OpenGL("wglGetExtensionsStringEXT"); + + if (proc) + extensions = ((const char* (*)(HDC))proc)(wglGetCurrentDC()); + else if (proc2) + extensions = ((const char*(*)(void))proc2)(); + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +} + +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { + RGFW_proc proc = (RGFW_proc)wglGetProcAddress(procname); + if (proc) + return proc; + + return (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); +} + +void RGFW_win32_loadOpenGLFuncs(HWND dummyWin) { + if (wglSwapIntervalEXT != NULL && wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB != NULL) + return; + + HDC dummy_dc = GetDC(dummyWin); + u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 32, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 32, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0}; + + int dummy_pixel_format = ChoosePixelFormat(dummy_dc, &pfd); + SetPixelFormat(dummy_dc, dummy_pixel_format, &pfd); + + HGLRC dummy_context = wglCreateContext(dummy_dc); + + HGLRC cur = wglGetCurrentContext(); + wglMakeCurrent(dummy_dc, dummy_context); + + wglCreateContextAttribsARB = ((PFNWGLCREATECONTEXTATTRIBSARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglCreateContextAttribsARB"); + wglChoosePixelFormatARB = ((PFNWGLCHOOSEPIXELFORMATARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglChoosePixelFormatARB"); + + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(RGFW_proc)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapIntervalEXT == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function"); + } + + wglMakeCurrent(dummy_dc, cur); + wglDeleteContext(dummy_context); + ReleaseDC(dummyWin, dummy_dc); +} + +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_STEREO_ARB 0x2012 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ACCUM_RED_BITS_ARB 0x201e +#define WGL_ACCUM_GREEN_BITS_ARB 0x201f +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_COLORSPACE_SRGB_EXT 0x3089 +#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0x0000 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_ACCESS_READ_WRITE_NV 0x00000001 +#define WGL_COVERAGE_SAMPLES_NV 0x2042 +#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 + +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + const char flushControl[] = "WGL_ARB_context_flush_control"; + const char noError[] = "WGL_ARB_create_context_no_error"; + const char robustness[] = "WGL_ARB_create_context_robustness"; + + win->src.ctx.native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + + PIXELFORMATDESCRIPTOR pfd; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.iLayerType = PFD_MAIN_PLANE; + pfd.cColorBits = 32; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = (BYTE)hints->stencil; + pfd.cAuxBuffers = (BYTE)hints->auxBuffers; + if (hints->stereo) pfd.dwFlags |= PFD_STEREO; + + /* try to create the pixel format we want for OpenGL and then try to create an OpenGL context for the specified version */ + if (hints->renderer == RGFW_glSoftware) + pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; + + /* get pixel format, default to a basic pixel format */ + int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); + if (wglChoosePixelFormatARB != NULL) { + i32 pixel_format_attribs[50]; + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, pixel_format_attribs, 50); + + RGFW_attribStack_pushAttribs(&stack, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB); + RGFW_attribStack_pushAttribs(&stack, WGL_DRAW_TO_WINDOW_ARB, 1); + RGFW_attribStack_pushAttribs(&stack, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB); + RGFW_attribStack_pushAttribs(&stack, WGL_SUPPORT_OPENGL_ARB, 1); + RGFW_attribStack_pushAttribs(&stack, WGL_COLOR_BITS_ARB, 32); + RGFW_attribStack_pushAttribs(&stack, WGL_DOUBLE_BUFFER_ARB, 1); + RGFW_attribStack_pushAttribs(&stack, WGL_ALPHA_BITS_ARB, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, WGL_DEPTH_BITS_ARB, hints->depth); + RGFW_attribStack_pushAttribs(&stack, WGL_STENCIL_BITS_ARB, hints->stencil); + RGFW_attribStack_pushAttribs(&stack, WGL_STEREO_ARB, hints->stereo); + RGFW_attribStack_pushAttribs(&stack, WGL_AUX_BUFFERS_ARB, hints->auxBuffers); + RGFW_attribStack_pushAttribs(&stack, WGL_RED_BITS_ARB, hints->red); + RGFW_attribStack_pushAttribs(&stack, WGL_GREEN_BITS_ARB, hints->blue); + RGFW_attribStack_pushAttribs(&stack, WGL_BLUE_BITS_ARB, hints->green); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_RED_BITS_ARB, hints->accumRed); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_GREEN_BITS_ARB, hints->accumGreen); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_BLUE_BITS_ARB, hints->accumBlue); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_ALPHA_BITS_ARB, hints->accumAlpha); + + if(hints->sRGB) { + if (hints->profile != RGFW_glES) + RGFW_attribStack_pushAttribs(&stack, WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, 1); + else + RGFW_attribStack_pushAttribs(&stack, WGL_COLORSPACE_SRGB_EXT, hints->sRGB); + } + + RGFW_attribStack_pushAttribs(&stack, WGL_COVERAGE_SAMPLES_NV, hints->samples); + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + int new_pixel_format; + UINT num_formats; + wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &new_pixel_format, &num_formats); + if (!num_formats) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create a pixel format for WGL"); + else pixel_format = new_pixel_format; + } + + PIXELFORMATDESCRIPTOR suggested; + if (!DescribePixelFormat(win->src.hdc, pixel_format, sizeof(suggested), &suggested) || + !SetPixelFormat(win->src.hdc, pixel_format, &pfd)) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to set the WGL pixel format"); + + if (wglCreateContextAttribsARB != NULL) { + /* create OpenGL/WGL context for the specified version */ + i32 attribs[40]; + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, attribs, 50); + + + i32 mask = 0; + switch (hints->profile) { + case RGFW_glES: mask |= WGL_CONTEXT_ES_PROFILE_BIT_EXT; break; + case RGFW_glCompatibility: mask |= WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; break; + case RGFW_glForwardCompatibility: mask |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; break; + case RGFW_glCore: mask |= WGL_CONTEXT_CORE_PROFILE_BIT_ARB; break; + default: mask |= WGL_CONTEXT_CORE_PROFILE_BIT_ARB; break; + } + + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_PROFILE_MASK_ARB, mask); + + if (hints->minor || hints->major) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_MAJOR_VERSION_ARB, hints->major); + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_MINOR_VERSION_ARB, hints->minor); + } + + if (RGFW_extensionSupportedPlatform_OpenGL(noError, sizeof(noError))) + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_OPENGL_NO_ERROR_ARB, hints->noError); + + if (RGFW_extensionSupportedPlatform_OpenGL(flushControl, sizeof(flushControl))) { + if (hints->releaseBehavior == RGFW_glReleaseFlush) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB); /* WGL_CONTEXT_RELEASE_BEHAVIOR_ARB */ + } else if (hints->releaseBehavior == RGFW_glReleaseNone) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB); + } + } + + i32 flags = 0; + if (hints->debug) flags |= WGL_CONTEXT_DEBUG_BIT_ARB; + if (hints->robustness && RGFW_extensionSupportedPlatform_OpenGL(robustness, sizeof(robustness))) flags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB; + if (flags) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_FLAGS_ARB, flags); + } + + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + win->src.ctx.native->ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); + } + + if (wglCreateContextAttribsARB == NULL || win->src.ctx.native->ctx == NULL) { /* fall back to a default context (probably OpenGL 2 or something) */ + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create an accelerated OpenGL Context."); + win->src.ctx.native->ctx = wglCreateContext(win->src.hdc); + } + + ReleaseDC(win->src.window, win->src.hdc); + win->src.hdc = GetDC(win->src.window); + + if (hints->share) { + wglShareLists((HGLRC)RGFW_getCurrentContext_OpenGL(), hints->share->ctx); + } + + wglMakeCurrent(win->src.hdc, win->src.ctx.native->ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + return RGFW_TRUE; +} + +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + wglDeleteContext((HGLRC) ctx->ctx); /*!< delete OpenGL context */ + win->src.ctx.native->ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win == NULL) + wglMakeCurrent(NULL, NULL); + else + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx.native->ctx); +} +void* RGFW_getCurrentContext_OpenGL(void) { + return wglGetCurrentContext(); +} +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + RGFW_ASSERT(win->src.ctx.native); + SwapBuffers(win->src.hdc); +} + +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); + if (wglSwapIntervalEXT == NULL || wglSwapIntervalEXT(swapInterval) == FALSE) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to set swap interval"); +} +#endif + +RGFW_bool RGFW_createUTF8FromWideStringWin32(const WCHAR* source, char* output, size_t max) { + i32 size = 0; + if (source == NULL) { + return RGFW_FALSE; + } + size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL); + if (!size) { + return RGFW_FALSE; + } + + if (size > (i32)max) + size = (i32)max; + + if (!WideCharToMultiByte(CP_UTF8, 0, source, -1, output, size, NULL, NULL)) { + return RGFW_FALSE; + } + + output[size] = 0; + return RGFW_TRUE; +} + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUSurfaceSourceWindowsHWND fromHwnd = {0}; + fromHwnd.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + fromHwnd.hwnd = window->src.window; + + fromHwnd.hinstance = GetModuleHandle(NULL); + + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromHwnd.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + +#endif /* RGFW_WINDOWS */ + +/* + End of Windows defines +*/ + + + +/* + + Start of MacOS defines + + +*/ + +#if defined(RGFW_MACOS) +/* + based on silicon.h + start of cocoa wrapper +*/ + +#include +#include +#include +#include +#include + +#ifndef __OBJC__ +typedef CGRect NSRect; +typedef CGPoint NSPoint; +typedef CGSize NSSize; + +typedef const char* NSPasteboardType; +typedef unsigned long NSUInteger; +typedef long NSInteger; +typedef NSInteger NSModalResponse; + +typedef enum NSRequestUserAttentionType { + NSCriticalRequest = 0, + NSInformationalRequest = 10 +} NSRequestUserAttentionType; + +typedef enum NSApplicationActivationPolicy { + NSApplicationActivationPolicyRegular, + NSApplicationActivationPolicyAccessory, + NSApplicationActivationPolicyProhibited +} NSApplicationActivationPolicy; + +typedef RGFW_ENUM(u32, NSBackingStoreType) { + NSBackingStoreRetained = 0, + NSBackingStoreNonretained = 1, + NSBackingStoreBuffered = 2 +}; + +typedef RGFW_ENUM(u32, NSWindowStyleMask) { + NSWindowStyleMaskBorderless = 0, + NSWindowStyleMaskTitled = 1 << 0, + NSWindowStyleMaskClosable = 1 << 1, + NSWindowStyleMaskMiniaturizable = 1 << 2, + NSWindowStyleMaskResizable = 1 << 3, + NSWindowStyleMaskTexturedBackground = 1 << 8, /* deprecated */ + NSWindowStyleMaskUnifiedTitleAndToolbar = 1 << 12, + NSWindowStyleMaskFullScreen = 1 << 14, + NSWindowStyleMaskFullSizeContentView = 1 << 15, + NSWindowStyleMaskUtilityWindow = 1 << 4, + NSWindowStyleMaskDocModalWindow = 1 << 6, + NSWindowStyleMaskNonactivatingpanel = 1 << 7, + NSWindowStyleMaskHUDWindow = 1 << 13 +}; + +#define NSPasteboardTypeString "public.utf8-plain-text" + +typedef RGFW_ENUM(i32, NSDragOperation) { + NSDragOperationNone = 0, + NSDragOperationCopy = 1, + NSDragOperationLink = 2, + NSDragOperationGeneric = 4, + NSDragOperationPrivate = 8, + NSDragOperationMove = 16, + NSDragOperationDelete = 32, + NSDragOperationEvery = (int)ULONG_MAX +}; + +typedef RGFW_ENUM(NSInteger, NSOpenGLContextParameter) { + NSOpenGLContextParameterSwapInterval = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ + NSOpenGLContextParametectxaceOrder = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ + NSOpenGLContextParametectxaceOpacity = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ + NSOpenGLContextParametectxaceBackingSize = 304, /* 2 params. Width/height of surface backing size */ + NSOpenGLContextParameterReclaimResources = 308, /* 0 params. */ + NSOpenGLContextParameterCurrentRendererID = 309, /* 1 param. Retrieves the current renderer ID */ + NSOpenGLContextParameterGPUVertexProcessing = 310, /* 1 param. Currently processing vertices with GPU (get) */ + NSOpenGLContextParameterGPUFragmentProcessing = 311, /* 1 param. Currently processing fragments with GPU (get) */ + NSOpenGLContextParameterHasDrawable = 314, /* 1 param. Boolean returned if drawable is attached */ + NSOpenGLContextParameterMPSwapsInFlight = 315, /* 1 param. Max number of swaps queued by the MP GL engine */ + + NSOpenGLContextParameterSwapRectangle API_DEPRECATED("", macos(10.0, 10.14)) = 200, /* 4 params. Set or get the swap rectangle {x, y, w, h} */ + NSOpenGLContextParameterSwapRectangleEnable API_DEPRECATED("", macos(10.0, 10.14)) = 201, /* Enable or disable the swap rectangle */ + NSOpenGLContextParameterRasterizationEnable API_DEPRECATED("", macos(10.0, 10.14)) = 221, /* Enable or disable all rasterization */ + NSOpenGLContextParameterStateValidation API_DEPRECATED("", macos(10.0, 10.14)) = 301, /* Validate state for multi-screen functionality */ + NSOpenGLContextParametectxaceSurfaceVolatile API_DEPRECATED("", macos(10.0, 10.14)) = 306, /* 1 param. Surface volatile state */ +}; + +typedef RGFW_ENUM(NSInteger, NSWindowButton) { + NSWindowCloseButton = 0, + NSWindowMiniaturizeButton = 1, + NSWindowZoomButton = 2, + NSWindowToolbarButton = 3, + NSWindowDocumentIconButton = 4, + NSWindowDocumentVersionsButton = 6, + NSWindowFullScreenButton = 7, +}; + +#define NSPasteboardTypeURL "public.url" +#define NSPasteboardTypeFileURL "public.file-url" +#define NSTrackingMouseEnteredAndExited 0x01 +#define NSTrackingMouseMoved 0x02 +#define NSTrackingCursorUpdate 0x04 +#define NSTrackingActiveWhenFirstResponder 0x10 +#define NSTrackingActiveInKeyWindow 0x20 +#define NSTrackingActiveInActiveApp 0x40 +#define NSTrackingActiveAlways 0x80 +#define NSTrackingAssumeInside 0x100 +#define NSTrackingInVisibleRect 0x200 +#define NSTrackingEnabledDuringMouseDrag 0x400 +enum { + NSOpenGLPFAAllRenderers = 1, /* choose from all available renderers */ + NSOpenGLPFATripleBuffer = 3, /* choose a triple buffered pixel format */ + NSOpenGLPFADoubleBuffer = 5, /* choose a double buffered pixel format */ + NSOpenGLPFAAuxBuffers = 7, /* number of aux buffers */ + NSOpenGLPFAColorSize = 8, /* number of color buffer bits */ + NSOpenGLPFAAlphaSize = 11, /* number of alpha component bits */ + NSOpenGLPFADepthSize = 12, /* number of depth buffer bits */ + NSOpenGLPFAStencilSize = 13, /* number of stencil buffer bits */ + NSOpenGLPFAAccumSize = 14, /* number of accum buffer bits */ + NSOpenGLPFAMinimumPolicy = 51, /* never choose smaller buffers than requested */ + NSOpenGLPFAMaximumPolicy = 52, /* choose largest buffers of type requested */ + NSOpenGLPFASampleBuffers = 55, /* number of multi sample buffers */ + NSOpenGLPFASamples = 56, /* number of samples per multi sample buffer */ + NSOpenGLPFAAuxDepthStencil = 57, /* each aux buffer has its own depth stencil */ + NSOpenGLPFAColorFloat = 58, /* color buffers store floating point pixels */ + NSOpenGLPFAMultisample = 59, /* choose multisampling */ + NSOpenGLPFASupersample = 60, /* choose supersampling */ + NSOpenGLPFASampleAlpha = 61, /* request alpha filtering */ + NSOpenGLPFARendererID = 70, /* request renderer by ID */ + NSOpenGLPFANoRecovery = 72, /* disable all failure recovery systems */ + NSOpenGLPFAAccelerated = 73, /* choose a hardware accelerated renderer */ + NSOpenGLPFAClosestPolicy = 74, /* choose the closest color buffer to request */ + NSOpenGLPFABackingStore = 76, /* back buffer contents are valid after swap */ + NSOpenGLPFAScreenMask = 84, /* bit mask of supported physical screens */ + NSOpenGLPFAAllowOfflineRenderers = 96, /* allow use of offline renderers */ + NSOpenGLPFAAcceleratedCompute = 97, /* choose a hardware accelerated compute device */ + NSOpenGLPFAOpenGLProfile = 99, /* specify an OpenGL Profile to use */ + NSOpenGLProfileVersionLegacy = 0x1000, /* The requested profile is a legacy (pre-OpenGL 3.0) profile. */ + NSOpenGLProfileVersion3_2Core = 0x3200, /* The 3.2 Profile of OpenGL */ + NSOpenGLProfileVersion4_1Core = 0x3200, /* The 4.1 profile of OpenGL */ + NSOpenGLPFAVirtualScreenCount = 128, /* number of virtual screens in this format */ + NSOpenGLPFAStereo = 6, + NSOpenGLPFAOffScreen = 53, + NSOpenGLPFAFullScreen = 54, + NSOpenGLPFASingleRenderer = 71, + NSOpenGLPFARobust = 75, + NSOpenGLPFAMPSafe = 78, + NSOpenGLPFAWindow = 80, + NSOpenGLPFAMultiScreen = 81, + NSOpenGLPFACompliant = 83, + NSOpenGLPFAPixelBuffer = 90, + NSOpenGLPFARemotePixelBuffer = 91, +}; + +typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ + NSEventTypeApplicationDefined = 15, +}; +typedef unsigned long long NSEventMask; + +typedef enum NSEventModifierFlags { + NSEventModifierFlagCapsLock = 1 << 16, + NSEventModifierFlagShift = 1 << 17, + NSEventModifierFlagControl = 1 << 18, + NSEventModifierFlagOption = 1 << 19, + NSEventModifierFlagCommand = 1 << 20, + NSEventModifierFlagNumericPad = 1 << 21 +} NSEventModifierFlags; + +typedef RGFW_ENUM(NSUInteger, NSBitmapFormat) { + NSBitmapFormatAlphaFirst = 1 << 0, /* 0 means is alpha last (RGBA, CMYKA, etc.) */ + NSBitmapFormatAlphaNonpremultiplied = 1 << 1, /* 0 means is premultiplied */ + NSBitmapFormatFloatingpointSamples = 1 << 2, /* 0 is integer */ + + NSBitmapFormatSixteenBitLittleEndian = (1 << 8), + NSBitmapFormatThirtyTwoBitLittleEndian = (1 << 9), + NSBitmapFormatSixteenBitBigEndian = (1 << 10), + NSBitmapFormatThirtyTwoBitBigEndian = (1 << 11) +}; + +#else +#import +#include +#endif /* notdef __OBJC__ */ + +#ifdef __arm64__ + /* ARM just uses objc_msgSend */ +#define abi_objc_msgSend_stret objc_msgSend +#define abi_objc_msgSend_fpret objc_msgSend +#else /* __i386__ */ + /* x86 just uses abi_objc_msgSend_fpret and (NSColor *)objc_msgSend_id respectively */ +#define abi_objc_msgSend_stret objc_msgSend_stret +#define abi_objc_msgSend_fpret objc_msgSend_fpret +#endif + +#define NSAlloc(nsclass) objc_msgSend_id((id)nsclass, sel_registerName("alloc")) +#define objc_msgSend_bool(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void(x, y) ((void (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_id(x, y, z) ((void (*)(id, SEL, id))objc_msgSend) ((id)x, (SEL)y, (id)z) +#define objc_msgSend_uint(x, y) ((NSUInteger (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_bool(x, y, z) ((void (*)(id, SEL, BOOL))objc_msgSend) ((id)(x), (SEL)y, (BOOL)z) +#define objc_msgSend_bool_void(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_SEL(x, y, z) ((void (*)(id, SEL, SEL))objc_msgSend) ((id)(x), (SEL)y, (SEL)z) +#define objc_msgSend_id(x, y) ((id (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_id_id(x, y, z) ((id (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_id_bool(x, y, z) ((BOOL (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_int(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_arr(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_ptr(x, y, z) ((id (*)(id, SEL, void*))objc_msgSend) ((id)(x), (SEL)y, (void*)z) +#define objc_msgSend_class(x, y) ((id (*)(Class, SEL))objc_msgSend) ((Class)(x), (SEL)y) +#define objc_msgSend_class_char(x, y, z) ((id (*)(Class, SEL, char*))objc_msgSend) ((Class)(x), (SEL)y, (char*)z) + +#define NSRelease(obj) objc_msgSend_void((id)obj, sel_registerName("release")) +RGFWDEF id NSString_stringWithUTF8String(const char* str); +id NSString_stringWithUTF8String(const char* str) { + return ((id(*)(id, SEL, const char*))objc_msgSend) ((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), str); +} + +RGFWDEF float RGFW_cocoaYTransform(float y); +float RGFW_cocoaYTransform(float y) { return (float)(CGDisplayBounds(CGMainDisplayID()).size.height - (double)y - (double)1.0f); } + +const char* NSString_to_char(id str); +const char* NSString_to_char(id str) { + return ((const char* (*)(id, SEL)) objc_msgSend) ((id)(id)str, sel_registerName("UTF8String")); +} + +unsigned char* NSBitmapImageRep_bitmapData(id imageRep); +unsigned char* NSBitmapImageRep_bitmapData(id imageRep) { + return ((unsigned char* (*)(id, SEL))objc_msgSend) ((id)imageRep, sel_registerName("bitmapData")); +} + +id NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits); +id NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits) { + SEL func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); + + return (id) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, id, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) + (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); +} + +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { + Class nsclass = objc_getClass("NSColor"); + SEL func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); + return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) + ((id)nsclass, func, red, green, blue, alpha); +} + +id NSPasteboard_generalPasteboard(void); +id NSPasteboard_generalPasteboard(void) { + return (id) objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard")); +} + +id* cstrToNSStringArray(char** strs, size_t len); +id* cstrToNSStringArray(char** strs, size_t len) { + static id nstrs[6]; + size_t i; + for (i = 0; i < len; i++) + nstrs[i] = NSString_stringWithUTF8String(strs[i]); + + return nstrs; +} + +const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len); +const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len) { + SEL func = sel_registerName("stringForType:"); + id nsstr = NSString_stringWithUTF8String((const char*)dataType); + id nsString = ((id(*)(id, SEL, id))objc_msgSend)(pasteboard, func, nsstr); + const char* str = NSString_to_char(nsString); + if (len != NULL) + *len = (size_t)((NSUInteger(*)(id, SEL, int))objc_msgSend)(nsString, sel_registerName("maximumLengthOfBytesUsingEncoding:"), 4); + return str; +} + +id c_array_to_NSArray(void* array, size_t len); +id c_array_to_NSArray(void* array, size_t len) { + return ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend) (NSAlloc(objc_getClass("NSArray")), sel_registerName("initWithObjects:count:"), array, len); +} + + +void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len); +void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len) { + id* ntypes = cstrToNSStringArray((char**)newTypes, len); + + id array = c_array_to_NSArray(ntypes, len); + objc_msgSend_void_id(view, sel_registerName("registerForDraggedTypes:"), array); + NSRelease(array); +} + +NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner); +NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner) { + id* ntypes = cstrToNSStringArray((char**)newTypes, len); + + SEL func = sel_registerName("declareTypes:owner:"); + + id array = c_array_to_NSArray(ntypes, len); + + NSInteger output = ((NSInteger(*)(id, SEL, id, void*))objc_msgSend) + (pasteboard, func, array, owner); + NSRelease(array); + + return output; +} + +#define NSRetain(obj) objc_msgSend_void((id)obj, sel_registerName("retain")) + +/* + End of cocoa wrapper +*/ + +static id RGFW__osxCustomInitWithRGFWWindow(id self, SEL _cmd, RGFW_window* win) { + RGFW_UNUSED(_cmd); + struct objc_super s = { self, class_getSuperclass(object_getClass(self)) }; + self = ((id (*)(struct objc_super*, SEL))objc_msgSendSuper)(&s, sel_registerName("init")); + + if (self != nil) { + object_setInstanceVariable(self, "RGFW_window", win); + object_setInstanceVariable(self, "trackingArea", nil); + + object_setInstanceVariable( + self, "markedText", + ((id (*)(id, SEL))objc_msgSend)( + ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSMutableAttributedString"), sel_registerName("alloc")), + sel_registerName("init") + ) + ); + + ((void (*)(id, SEL))objc_msgSend)(self, sel_registerName("updateTrackingAreas")); + + ((void (*)(id, SEL, id))objc_msgSend)( + self, sel_registerName("registerForDraggedTypes:"), + ((id (*)(Class, SEL, id))objc_msgSend)( + objc_getClass("NSArray"), + sel_registerName("arrayWithObject:"), + ((id (*)(Class, SEL, const char*))objc_msgSend)( + objc_getClass("NSString"), + sel_registerName("stringWithUTF8String:"), + "public.url" + ) + ) + ); + } + + return self; +} + +static u32 RGFW_OnClose(id self) { + RGFW_window* win = NULL; + object_getInstanceVariable(self, (const char*)"RGFW_window", (void**)&win); + if (win == NULL) return true; + + RGFW_windowQuitCallback(win); + return false; +} + +/* NOTE(EimaMei): Fixes the constant clicking when the app is running under a terminal. */ +static bool RGFW__osxAcceptsFirstResponder(void) { return true; } +static bool RGFW__osxPerformKeyEquivalent(id event) { RGFW_UNUSED(event); return true; } + +static NSDragOperation RGFW__osxDraggingEntered(id self, SEL sel, id sender) { + RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); + + return NSDragOperationCopy; +} +static NSDragOperation RGFW__osxDraggingUpdated(id self, SEL sel, id sender) { + RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) + return 0; + if (!(win->internal.enabledEvents & RGFW_dataDragFlag)) return NSDragOperationCopy; + + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); + RGFW_dataDragCallback(win, (i32) p.x, (i32) (win->h - p.y)); + return NSDragOperationCopy; +} +static bool RGFW__osxPrepareForDragOperation(id self) { + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || (!(win->internal.enabledEvents & RGFW_dataDropFlag))) + return true; + + if (!(win->internal.flags & RGFW_windowAllowDND)) { + return false; + } + + return true; +} + +void RGFW__osxDraggingEnded(id self, SEL sel, id sender); +void RGFW__osxDraggingEnded(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return; } + +static bool RGFW__osxPerformDragOperation(id self, SEL sel, id sender) { + RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || (!(win->internal.enabledEvents & RGFW_dataDropFlag))) + return false; + + /* id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); */ + + id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); + + /* Get the types of data available on the pasteboard */ + id types = objc_msgSend_id(pasteBoard, sel_registerName("types")); + + /* Get the string type for file URLs */ + id fileURLsType = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "NSFilenamesPboardType"); + + /* Check if the pasteboard contains file URLs */ + if (objc_msgSend_id_bool(types, sel_registerName("containsObject:"), fileURLsType) == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "No files found on the pasteboard."); + return 0; + } + + id fileURLs = objc_msgSend_id_id(pasteBoard, sel_registerName("propertyListForType:"), fileURLsType); + int count = ((int (*)(id, SEL))objc_msgSend)(fileURLs, sel_registerName("count")); + + if (count == 0) + return 0; + + char** files = (char**)(void*)_RGFW->files; + + u32 i; + for (i = 0; i < (u32)count; i++) { + id fileURL = objc_msgSend_arr(fileURLs, sel_registerName("objectAtIndex:"), i); + const char *filePath = ((const char* (*)(id, SEL))objc_msgSend)(fileURL, sel_registerName("UTF8String")); + RGFW_STRNCPY(files[i], filePath, RGFW_MAX_PATH - 1); + files[i][RGFW_MAX_PATH - 1] = '\0'; + } + + RGFW_dataDropCallback(win, files, (size_t)count); + + return false; +} + +#ifndef RGFW_NO_IOKIT +#include + +float RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID); +float RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { + float refreshRate = 0; + io_iterator_t it; + io_service_t service; + CFNumberRef indexRef, clockRef, countRef; + u32 clock, count; + +#ifdef kIOMainPortDefault + if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) +#elif defined(kIOMasterPortDefault) + if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) +#endif + return RGFW_FALSE; + + while ((service = IOIteratorNext(it)) != 0) { + u32 index; + indexRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFramebufferOpenGLIndex"), kCFAllocatorDefault, kNilOptions); + if (indexRef == 0) continue; + + if (CFNumberGetValue(indexRef, kCFNumberIntType, &index) && CGOpenGLDisplayMaskToDisplayID(1 << index) == displayID) { + CFRelease(indexRef); + break; + } + + CFRelease(indexRef); + } + + if (service) { + clockRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelClock"), kCFAllocatorDefault, kNilOptions); + if (clockRef) { + if (CFNumberGetValue(clockRef, kCFNumberIntType, &clock) && clock) { + countRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelCount"), kCFAllocatorDefault, kNilOptions); + if (countRef && CFNumberGetValue(countRef, kCFNumberIntType, &count) && count) { + refreshRate = (float)((double)clock / (double) count); + CFRelease(countRef); + } + } + CFRelease(clockRef); + } + } + + IOObjectRelease(it); + return refreshRate; +} +#endif + +void RGFW_moveToMacOSResourceDir(void) { + char resourcesPath[256]; + + CFBundleRef bundle = CFBundleGetMainBundle(); + if (!bundle) + return; + + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); + CFStringRef last = CFURLCopyLastPathComponent(resourcesURL); + + if ( + CFStringCompare(CFSTR("Resources"), last, 0) != kCFCompareEqualTo || + CFURLGetFileSystemRepresentation(resourcesURL, true, (u8*) resourcesPath, 255) == 0 + ) { + CFRelease(last); + CFRelease(resourcesURL); + return; + } + + CFRelease(last); + CFRelease(resourcesURL); + + chdir(resourcesPath); +} + +static void RGFW__osxDidChangeScreenParameters(id self, SEL _cmd, id notification) { + RGFW_UNUSED(self); RGFW_UNUSED(_cmd); RGFW_UNUSED(notification); + RGFW_pollMonitors(); +} + +static void RGFW__osxWindowDeminiaturize(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); + +} +static void RGFW__osxWindowMiniaturize(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_windowMinimizedCallback(win); + +} + +static void RGFW__osxWindowBecameKey(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_focusCallback(win, RGFW_TRUE); +} + +static void RGFW__osxWindowResignKey(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_focusCallback(win, RGFW_FALSE); +} + +static void RGFW__osxDidWindowResize(id self, SEL _cmd, id notification) { + RGFW_UNUSED(_cmd); RGFW_UNUSED(notification); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + NSRect frame; + if (win->src.view) frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); + else return; + + if (frame.size.width == 0 || frame.size.height == 0) return; + win->w = (i32)frame.size.width; + win->h = (i32)frame.size.height; + + RGFW_monitor* mon = RGFW_window_getMonitor(win); + if (mon == NULL) return; + + if ((i32)mon->mode.w == win->w && (i32)mon->mode.h - 102 <= win->h) { + RGFW_windowMaximizedCallback(win, 0, 0, win->w, win->h); + } else if (win->internal.flags & RGFW_windowMaximize) { + RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); + } + + RGFW_windowResizedCallback(win, win->w, win->h); +} + +static void RGFW__osxWindowMove(id self, SEL sel) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); + NSRect content = ((NSRect(*)(id, SEL, NSRect))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("contentRectForFrameRect:"), frame); + + float y = RGFW_cocoaYTransform((float)(content.origin.y + content.size.height - 1)); + + RGFW_windowMovedCallback(win, (i32)content.origin.x, (i32)y); +} + +static void RGFW__osxViewDidChangeBackingProperties(id self, SEL _cmd) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_monitor* mon = RGFW_window_getMonitor(win); + if (mon == NULL) return; + + RGFW_scaleUpdatedCallback(win, mon->scaleX, mon->scaleY); +} + +static BOOL RGFW__osxWantsUpdateLayer(id self, SEL _cmd) { RGFW_UNUSED(self); RGFW_UNUSED(_cmd); return YES; } + +static void RGFW__osxUpdateLayer(id self, SEL _cmd) { + RGFW_UNUSED(self); RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + RGFW_windowRefreshCallback(win); +} + +static void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { + RGFW_UNUSED(rect); RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_windowRefreshCallback(win); +} + +static void RGFW__osxMouseEntered(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + NSPoint p = ((NSPoint(*)(id, SEL))objc_msgSend)(event, sel_registerName("locationInWindow")); + RGFW_mouseNotifyCallback(win, (i32)p.x, (i32)(win->h - p.y), 1); +} + +static void RGFW__osxMouseExited(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); RGFW_UNUSED(event); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_mouseNotifyCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, RGFW_FALSE); +} + +static void RGFW__osxKeyDown(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_keyPressedFlag)) return; + + u32 key = (u16)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); + u32 mappedKey = (u32)*(((char*)(const char*)NSString_to_char(((id(*)(id, SEL))objc_msgSend)(event, sel_registerName("charactersIgnoringModifiers"))))); + if ((u8)mappedKey == 239) mappedKey = 0; + + RGFW_key value = (u8)RGFW_apiKeyToRGFW(key); + RGFW_bool repeat = RGFW_window_isKeyPressed(win, value); + + RGFW_keyCallback(win, value, (u8)mappedKey, win->internal.mod, repeat, 1); +} + +static void RGFW__osxKeyUp(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return; + + u32 key = (u16)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); + u32 mappedKey = (u32)*(((char*)(const char*)NSString_to_char(((id(*)(id, SEL))objc_msgSend)(event, sel_registerName("charactersIgnoringModifiers"))))); + if ((u8)mappedKey == 239) mappedKey = 0; + + RGFW_key value = (u8)RGFW_apiKeyToRGFW(key); + RGFW_bool repeat = RGFW_window_isKeyDown(win, (u8)value); + + RGFW_keyCallback(win, value, (u8)mappedKey, win->internal.mod, repeat, 1); +} + +static void RGFW__osxFlagsChanged(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_key value = 0; + RGFW_bool pressed = RGFW_FALSE; + + u32 flags = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("modifierFlags")); + RGFW_updateKeyModsEx(win, + ((u32)(flags & NSEventModifierFlagCapsLock) % 255), + ((flags & NSEventModifierFlagNumericPad) % 255), + ((flags & NSEventModifierFlagControl) % 255), + ((flags & NSEventModifierFlagOption) % 255), + ((flags & NSEventModifierFlagShift) % 255), + ((flags & NSEventModifierFlagCommand) % 255), 0); + u8 i; + for (i = 0; i < 9; i++) + _RGFW->keyboard[i + RGFW_capsLock].prev = _RGFW->keyboard[i + RGFW_capsLock].current; + + for (i = 0; i < 5; i++) { + u32 shift = (1 << (i + 16)); + u32 key = i + RGFW_capsLock; + if ((flags & shift) && !RGFW_window_isKeyDown(win, (u8)key)) { + pressed = RGFW_TRUE; + value = (u8)key; + break; + } + if (!(flags & shift) && RGFW_window_isKeyDown(win, (u8)key)) { + pressed = RGFW_FALSE; + value = (u8)key; + break; + } + } + + RGFW_bool repeat = RGFW_window_isKeyDown(win, (u8)value); + RGFW_keyCallback(win, value, 0, win->internal.mod, repeat, pressed); + + if (value != RGFW_capsLock) { + RGFW_keyCallback(win, value + 4, 0, win->internal.mod, repeat, pressed); + } + +} + +static void RGFW__osxMouseMoved(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + NSPoint p = ((NSPoint(*)(id, SEL))objc_msgSend)(event, sel_registerName("locationInWindow")); + + CGFloat vecX = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaX")); + CGFloat vecY = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaY")); + + RGFW_mousePosCallback(win, (i32)p.x, (i32)(win->h - p.y), (float)vecX, (float)vecY); +} + +static void RGFW__osxMouseDown(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + u32 buttonNumber = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); + + RGFW_mouseButton value = 0; + switch (buttonNumber) { + case 0: value = RGFW_mouseLeft; break; + case 1: value = RGFW_mouseRight; break; + case 2: value = RGFW_mouseMiddle; break; + default: value = (u8)buttonNumber; + } + + RGFW_mouseButtonCallback(win, value, 1); +} + +static void RGFW__osxMouseUp(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + u32 buttonNumber = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); + + RGFW_mouseButton value = 0; + switch (buttonNumber) { + case 0: value = RGFW_mouseLeft; break; + case 1: value = RGFW_mouseRight; break; + case 2: value = RGFW_mouseMiddle; break; + default: value = (u8)buttonNumber; + } + + RGFW_mouseButtonCallback(win, value, 0); +} + +static void RGFW__osxScrollWheel(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + float deltaX = (float)((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaX")); + float deltaY = (float)((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaY")); + + RGFW_mouseScrollCallback(win, deltaX, deltaY); +} + +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + surface->native.format = RGFW_formatRGBA8; + + surface->native.buffer = (u8*)RGFW_ALLOC((size_t)(w * h * 4)); + return RGFW_TRUE; +} + +void RGFW_surface_freePtr(RGFW_surface* surface) { RGFW_FREE(surface->native.buffer); } + +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + + int minX = RGFW_MIN(win->w, surface->w); + int minY = RGFW_MIN(win->h, surface->h); + + RGFW_monitor* mon = RGFW_window_getMonitor(win); + if (mon == NULL) return; + + minX = (i32)((float)minX * mon->pixelRatio); + minY = (i32)((float)minY * mon->pixelRatio); + + RGFW_copyImageData(surface->native.buffer, surface->w, minY, surface->native.format, surface->data, surface->format); + + id image = ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSImage"), sel_getUid("alloc")); + NSSize size = (NSSize){(double)surface->w, (double)surface->h}; + image = ((id (*)(id, SEL, NSSize))objc_msgSend)((id)image, sel_getUid("initWithSize:"), size); + + id rep = NSBitmapImageRep_initWithBitmapData(&surface->native.buffer, minX, minY, 8, 4, true, false, "NSDeviceRGBColorSpace", 1 << 1, (u32)surface->w * 4, 32); + RGFW_copyImageData(NSBitmapImageRep_bitmapData(rep), minX, minY , RGFW_formatRGBA8, surface->native.buffer, surface->native.format); + ((void (*)(id, SEL, id))objc_msgSend)((id)image, sel_getUid("addRepresentation:"), rep); + + id contentView = ((id (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setWantsLayer:"), YES); + id layer = ((id (*)(id, SEL))objc_msgSend)(contentView, sel_getUid("layer")); + + ((void (*)(id, SEL, id))objc_msgSend)(layer, sel_getUid("setContents:"), (id)image); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setNeedsDisplay:"), YES); + + NSRelease(rep); + NSRelease(image); + + objc_msgSend_bool_void(pool, sel_registerName("drain")); +} + +void* RGFW_window_getView_OSX(RGFW_window* win) { return win->src.view; } + +void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer) { + objc_msgSend_void_id((id)win->src.view, sel_registerName("setLayer:"), (id)layer); +} + +void* RGFW_getLayer_OSX(void) { + return objc_msgSend_class((id)objc_getClass("CAMetalLayer"), (SEL)sel_registerName("layer")); +} +void* RGFW_window_getWindow_OSX(RGFW_window* win) { return win->src.window; } + +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[0x1D] = RGFW_0; + _RGFW->keycodes[0x12] = RGFW_1; + _RGFW->keycodes[0x13] = RGFW_2; + _RGFW->keycodes[0x14] = RGFW_3; + _RGFW->keycodes[0x15] = RGFW_4; + _RGFW->keycodes[0x17] = RGFW_5; + _RGFW->keycodes[0x16] = RGFW_6; + _RGFW->keycodes[0x1A] = RGFW_7; + _RGFW->keycodes[0x1C] = RGFW_8; + _RGFW->keycodes[0x19] = RGFW_9; + _RGFW->keycodes[0x00] = RGFW_a; + _RGFW->keycodes[0x0B] = RGFW_b; + _RGFW->keycodes[0x08] = RGFW_c; + _RGFW->keycodes[0x02] = RGFW_d; + _RGFW->keycodes[0x0E] = RGFW_e; + _RGFW->keycodes[0x03] = RGFW_f; + _RGFW->keycodes[0x05] = RGFW_g; + _RGFW->keycodes[0x04] = RGFW_h; + _RGFW->keycodes[0x22] = RGFW_i; + _RGFW->keycodes[0x26] = RGFW_j; + _RGFW->keycodes[0x28] = RGFW_k; + _RGFW->keycodes[0x25] = RGFW_l; + _RGFW->keycodes[0x2E] = RGFW_m; + _RGFW->keycodes[0x2D] = RGFW_n; + _RGFW->keycodes[0x1F] = RGFW_o; + _RGFW->keycodes[0x23] = RGFW_p; + _RGFW->keycodes[0x0C] = RGFW_q; + _RGFW->keycodes[0x0F] = RGFW_r; + _RGFW->keycodes[0x01] = RGFW_s; + _RGFW->keycodes[0x11] = RGFW_t; + _RGFW->keycodes[0x20] = RGFW_u; + _RGFW->keycodes[0x09] = RGFW_v; + _RGFW->keycodes[0x0D] = RGFW_w; + _RGFW->keycodes[0x07] = RGFW_x; + _RGFW->keycodes[0x10] = RGFW_y; + _RGFW->keycodes[0x06] = RGFW_z; + _RGFW->keycodes[0x27] = RGFW_apostrophe; + _RGFW->keycodes[0x2A] = RGFW_backSlash; + _RGFW->keycodes[0x2B] = RGFW_comma; + _RGFW->keycodes[0x18] = RGFW_equals; + _RGFW->keycodes[0x32] = RGFW_backtick; + _RGFW->keycodes[0x21] = RGFW_bracket; + _RGFW->keycodes[0x1B] = RGFW_minus; + _RGFW->keycodes[0x2F] = RGFW_period; + _RGFW->keycodes[0x1E] = RGFW_closeBracket; + _RGFW->keycodes[0x29] = RGFW_semicolon; + _RGFW->keycodes[0x2C] = RGFW_slash; + _RGFW->keycodes[0x0A] = RGFW_world1; + _RGFW->keycodes[0x33] = RGFW_backSpace; + _RGFW->keycodes[0x39] = RGFW_capsLock; + _RGFW->keycodes[0x75] = RGFW_delete; + _RGFW->keycodes[0x7D] = RGFW_down; + _RGFW->keycodes[0x77] = RGFW_end; + _RGFW->keycodes[0x24] = RGFW_enter; + _RGFW->keycodes[0x35] = RGFW_escape; + _RGFW->keycodes[0x7A] = RGFW_F1; + _RGFW->keycodes[0x78] = RGFW_F2; + _RGFW->keycodes[0x63] = RGFW_F3; + _RGFW->keycodes[0x76] = RGFW_F4; + _RGFW->keycodes[0x60] = RGFW_F5; + _RGFW->keycodes[0x61] = RGFW_F6; + _RGFW->keycodes[0x62] = RGFW_F7; + _RGFW->keycodes[0x64] = RGFW_F8; + _RGFW->keycodes[0x65] = RGFW_F9; + _RGFW->keycodes[0x6D] = RGFW_F10; + _RGFW->keycodes[0x67] = RGFW_F11; + _RGFW->keycodes[0x6F] = RGFW_F12; + _RGFW->keycodes[0x69] = RGFW_printScreen; + _RGFW->keycodes[0x6B] = RGFW_F14; + _RGFW->keycodes[0x71] = RGFW_F15; + _RGFW->keycodes[0x6A] = RGFW_F16; + _RGFW->keycodes[0x40] = RGFW_F17; + _RGFW->keycodes[0x4F] = RGFW_F18; + _RGFW->keycodes[0x50] = RGFW_F19; + _RGFW->keycodes[0x5A] = RGFW_F20; + _RGFW->keycodes[0x73] = RGFW_home; + _RGFW->keycodes[0x72] = RGFW_insert; + _RGFW->keycodes[0x7B] = RGFW_left; + _RGFW->keycodes[0x3A] = RGFW_altL; + _RGFW->keycodes[0x3B] = RGFW_controlL; + _RGFW->keycodes[0x38] = RGFW_shiftL; + _RGFW->keycodes[0x37] = RGFW_superL; + _RGFW->keycodes[0x6E] = RGFW_menu; + _RGFW->keycodes[0x47] = RGFW_numLock; + _RGFW->keycodes[0x79] = RGFW_pageDown; + _RGFW->keycodes[0x74] = RGFW_pageUp; + _RGFW->keycodes[0x7C] = RGFW_right; + _RGFW->keycodes[0x3D] = RGFW_altR; + _RGFW->keycodes[0x3E] = RGFW_controlR; + _RGFW->keycodes[0x3C] = RGFW_shiftR; + _RGFW->keycodes[0x36] = RGFW_superR; + _RGFW->keycodes[0x31] = RGFW_space; + _RGFW->keycodes[0x30] = RGFW_tab; + _RGFW->keycodes[0x7E] = RGFW_up; + _RGFW->keycodes[0x52] = RGFW_kp0; + _RGFW->keycodes[0x53] = RGFW_kp1; + _RGFW->keycodes[0x54] = RGFW_kp2; + _RGFW->keycodes[0x55] = RGFW_kp3; + _RGFW->keycodes[0x56] = RGFW_kp4; + _RGFW->keycodes[0x57] = RGFW_kp5; + _RGFW->keycodes[0x58] = RGFW_kp6; + _RGFW->keycodes[0x59] = RGFW_kp7; + _RGFW->keycodes[0x5B] = RGFW_kp8; + _RGFW->keycodes[0x5C] = RGFW_kp9; + _RGFW->keycodes[0x45] = RGFW_kpSlash; + _RGFW->keycodes[0x41] = RGFW_kpPeriod; + _RGFW->keycodes[0x4B] = RGFW_kpSlash; + _RGFW->keycodes[0x4C] = RGFW_kpReturn; + _RGFW->keycodes[0x51] = RGFW_kpEqual; + _RGFW->keycodes[0x43] = RGFW_kpMultiply; + _RGFW->keycodes[0x4E] = RGFW_kpMinus; +} + +i32 RGFW_initPlatform(void) { + class_addMethod(objc_getClass("NSObject"), sel_registerName("windowShouldClose:"), (IMP)(void*)RGFW_OnClose, 0); + + /* NOTE(EimaMei): Fixes the 'Boop' sfx from constantly playing each time you click a key. Only a problem when running in the terminal. */ + class_addMethod(objc_getClass("NSWindowClass"), sel_registerName("acceptsFirstResponder:"), (IMP)(void*)RGFW__osxAcceptsFirstResponder, 0); + class_addMethod(objc_getClass("NSWindowClass"), sel_registerName("performKeyEquivalent:"), (IMP)(void*)RGFW__osxPerformKeyEquivalent, 0); + + _RGFW->NSApp = objc_msgSend_id(objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + + _RGFW->customNSAppDelegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "RGFWNSAppDelegate", 0); + class_addMethod((Class)_RGFW->customNSAppDelegateClass, sel_registerName("applicationDidChangeScreenParameters:"), (IMP)RGFW__osxDidChangeScreenParameters, "v@:@"); + objc_registerClassPair((Class)_RGFW->customNSAppDelegateClass); + _RGFW->customNSAppDelegate = objc_msgSend_id(NSAlloc(_RGFW->customNSAppDelegateClass), sel_registerName("init")); + + objc_msgSend_void_id(_RGFW->NSApp, sel_registerName("setDelegate:"), _RGFW->customNSAppDelegate); + + _RGFW->customViewClasses[0] = objc_allocateClassPair(objc_getClass("NSView"), "RGFWCustomView", 0); + _RGFW->customViewClasses[1] = objc_allocateClassPair(objc_getClass("NSOpenGLView"), "RGFWOpenGLCustomView", 0); + for (size_t i = 0; i < 2; i++) { + class_addIvar((Class)_RGFW->customViewClasses[i], "RGFW_window", sizeof(RGFW_window*), sizeof(RGFW_window*), "L"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("drawRect:"), (IMP)RGFW__osxDrawRect, "v@:{CGRect=ffff}"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("viewDidChangeBackingProperties"), (IMP)RGFW__osxViewDidChangeBackingProperties, "v@:"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("scrollWheel:"), (IMP)RGFW__osxScrollWheel, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("keyDown:"), (IMP)RGFW__osxKeyDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("keyUp:"), (IMP)RGFW__osxKeyUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseMoved:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseEntered:"), (IMP)RGFW__osxMouseEntered, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseExited:"), (IMP)RGFW__osxMouseExited, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("flagsChanged:"), (IMP)RGFW__osxFlagsChanged, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_getUid("acceptsFirstResponder"), (IMP)RGFW__osxAcceptsFirstResponder, "B@:"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("initWithRGFWWindow:"), (IMP)RGFW__osxCustomInitWithRGFWWindow, "@@:{CGRect={CGPoint=dd}{CGSize=dd}}"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("wantsUpdateLayer"), (IMP)RGFW__osxWantsUpdateLayer, "B@:"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("updateLayer"), (IMP)RGFW__osxUpdateLayer, "v@:"); + objc_registerClassPair((Class)_RGFW->customViewClasses[i]); + } + + _RGFW->customWindowDelegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "RGFWWindowDelegate", 0); + class_addIvar((Class)_RGFW->customWindowDelegateClass, "RGFW_window", sizeof(RGFW_window*), sizeof(RGFW_window*), "L"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidResize:"), (IMP)RGFW__osxDidWindowResize, "v@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidMiniaturize:"), (IMP) RGFW__osxWindowMiniaturize, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidDeminiaturize:"), (IMP) RGFW__osxWindowDeminiaturize, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidBecomeKey:"), (IMP) RGFW__osxWindowBecameKey, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidResignKey:"), (IMP) RGFW__osxWindowResignKey, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingEntered:"), (IMP)RGFW__osxDraggingEntered, "l@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingUpdated:"), (IMP)RGFW__osxDraggingUpdated, "l@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingExited:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingEnded:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("prepareForDragOperation:"), (IMP)RGFW__osxPrepareForDragOperation, "B@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("performDragOperation:"), (IMP)RGFW__osxPerformDragOperation, "B@:@"); + objc_registerClassPair((Class)_RGFW->customWindowDelegateClass); + return 0; +} + +void RGFW_osx_initView(RGFW_window* win) { + NSRect contentRect; + contentRect.origin.x = 0; + contentRect.origin.y = 0; + contentRect.size.width = (double)win->w; + contentRect.size.height = (double)win->h; + ((void(*)(id, SEL, CGRect))objc_msgSend)((id)win->src.view, sel_registerName("setFrame:"), contentRect); + + + if (RGFW_COCOA_FRAME_NAME) + objc_msgSend_ptr(win->src.view, sel_registerName("setFrameAutosaveName:"), RGFW_COCOA_FRAME_NAME); + + object_setInstanceVariable((id)win->src.view, "RGFW_window", win); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); + objc_msgSend_void_bool(win->src.view, sel_registerName("setWantsLayer:"), true); + objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); + + id trackingArea = objc_msgSend_id(objc_getClass("NSTrackingArea"), sel_registerName("alloc")); + trackingArea = ((id (*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( + trackingArea, + sel_registerName("initWithRect:options:owner:userInfo:"), + contentRect, + NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect, + (id)win->src.view, + nil + ); + + ((void (*)(id, SEL, id))objc_msgSend)((id)win->src.view, sel_registerName("addTrackingArea:"), trackingArea); + ((void (*)(id, SEL))objc_msgSend)(trackingArea, sel_registerName("release")); +} + +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { + /* RR Create an autorelease pool */ + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + + RGFW_window_setMouseDefault(win); + + NSRect windowRect; + windowRect.origin.x = (double)win->x; + windowRect.origin.y = (double)RGFW_cocoaYTransform((float)(win->y + win->h - 1)); + windowRect.size.width = (double)win->w; + windowRect.size.height = (double)win->h; + NSBackingStoreType macArgs = (NSBackingStoreType)(NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSBackingStoreBuffered | NSWindowStyleMaskTitled); + + if (!(flags & RGFW_windowNoResize)) + macArgs = (NSBackingStoreType)(macArgs | (NSBackingStoreType)NSWindowStyleMaskResizable); + if (!(flags & RGFW_windowNoBorder)) + macArgs = (NSBackingStoreType)(macArgs | (NSBackingStoreType)NSWindowStyleMaskTitled); + { + void* nsclass = objc_getClass("NSWindow"); + SEL func = sel_registerName("initWithContentRect:styleMask:backing:defer:"); + + win->src.window = ((id(*)(id, SEL, NSRect, NSWindowStyleMask, NSBackingStoreType, bool))objc_msgSend) + (NSAlloc(nsclass), func, windowRect, (NSWindowStyleMask)macArgs, macArgs, false); + } + + id str = NSString_stringWithUTF8String(name); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); + + win->src.delegate = (void*)objc_msgSend_id(NSAlloc((Class)_RGFW->customWindowDelegateClass), sel_registerName("init")); + object_setInstanceVariable((id)win->src.delegate, "RGFW_window", win); + + objc_msgSend_void_id((id)win->src.window, sel_registerName("setDelegate:"), (id)win->src.delegate); + + if (flags & RGFW_windowAllowDND) { + win->internal.flags |= RGFW_windowAllowDND; + + NSPasteboardType types[] = {NSPasteboardTypeURL, NSPasteboardTypeFileURL, NSPasteboardTypeString}; + NSregisterForDraggedTypes((id)win->src.window, types, 3); + } + + objc_msgSend_void_bool((id)win->src.window, sel_registerName("setAcceptsMouseMovedEvents:"), true); + + if (flags & RGFW_windowTransparent) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), false); + + objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), + NSColor_colorWithSRGB(0, 0, 0, 0)); + } + + /* Show the window */ + objc_msgSend_void_bool((id)_RGFW->NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + + if (_RGFW->root == NULL) { + objc_msgSend_void(win->src.window, sel_registerName("makeMainWindow")); + } + + objc_msgSend_void(win->src.window, sel_registerName("makeKeyWindow")); + + objc_msgSend_void((id)_RGFW->NSApp, sel_registerName("finishLaunching")); + NSRetain(win->src.window); + NSRetain(_RGFW->NSApp); + + win->src.view = ((id(*)(id, SEL, RGFW_window*))objc_msgSend) (NSAlloc((Class)_RGFW->customViewClasses[0]), sel_registerName("initWithRGFWWindow:"), win); + return win; +} + +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { + NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); + NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); + double offset = 0; + + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + NSBackingStoreType storeType = (NSBackingStoreType)(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullSizeContentView); + if (border) + storeType = (NSBackingStoreType)(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable); + if (!(win->internal.flags & RGFW_windowNoResize)) { + storeType = (NSBackingStoreType)(storeType | (NSBackingStoreType)NSWindowStyleMaskResizable); + } + + ((void (*)(id, SEL, NSBackingStoreType))objc_msgSend)((id)win->src.window, sel_registerName("setStyleMask:"), storeType); + + if (!border) { + id miniaturizeButton = objc_msgSend_int((id)win->src.window, sel_registerName("standardWindowButton:"), NSWindowMiniaturizeButton); + id titleBarView = objc_msgSend_id(miniaturizeButton, sel_registerName("superview")); + objc_msgSend_void_bool(titleBarView, sel_registerName("setHidden:"), true); + + offset = (double)(frame.size.height - content.size.height); + } + + RGFW_window_resize(win, win->w, win->h + (i32)offset); + win->h -= (i32)offset; +} + +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { + RGFW_ASSERT(_RGFW->root != NULL); + + CGEventRef e = CGEventCreate(NULL); + CGPoint point = CGEventGetLocation(e); + CFRelease(e); + + if (x) *x = (i32)point.x; + if (y) *y = (i32)point.y; + return RGFW_TRUE; +} + +void RGFW_stopCheckEvents(void) { + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + + id e = (id) ((id(*)(Class, SEL, NSEventType, NSPoint, NSEventModifierFlags, void*, NSInteger, void**, short, NSInteger, NSInteger))objc_msgSend) + (objc_getClass("NSEvent"), sel_registerName("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"), + NSEventTypeApplicationDefined, (NSPoint){0, 0}, (NSEventModifierFlags)0, NULL, (NSInteger)0, NULL, 0, 0, 0); + + ((void (*)(id, SEL, id, bool))objc_msgSend) + ((id)_RGFW->NSApp, sel_registerName("postEvent:atStart:"), e, 1); + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); +} + +void RGFW_waitForEvent(i32 waitMS) { + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + + void* date = (void*) ((id(*)(Class, SEL, double))objc_msgSend) + (objc_getClass("NSDate"), sel_registerName("dateWithTimeIntervalSinceNow:"), waitMS); + + SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); + id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) + ((id)_RGFW->NSApp, eventFunc, + ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + + if (e) { + ((void (*)(id, SEL, id, bool))objc_msgSend) + ((id)_RGFW->NSApp, sel_registerName("postEvent:atStart:"), e, 1); + } + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); +} + +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + return (u8)rgfw_keycode; /* TODO */ +} + +void RGFW_pollEvents(void) { + RGFW_resetPrevState(); + + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); + + while (1) { + void* date = NULL; + id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) + ((id)_RGFW->NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + + if (e == NULL) { + break; + } + + objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("sendEvent:"), e); + } + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); +} + + +void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + + NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); + + win->x = x; + win->y = (i32)RGFW_cocoaYTransform((float)y + (float)content.size.height - 1.0f); + + ((void(*)(id,SEL,NSPoint))objc_msgSend)((id)win->src.window, sel_registerName("setFrameOrigin:"), (NSPoint){(double)x, (double)y}); +} + +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + + NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); + NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); + float offset = (float)(frame.size.height - content.size.height); + + win->w = w; + win->h = h; + + + ((void(*)(id, SEL, CGRect))objc_msgSend)((id)win->src.view, sel_registerName("setFrame:"), (NSRect){{0, 0}, {(double)win->w, (double)win->h}}); + ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) + ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{(double)win->x, (double)win->y}, {(double)win->w, (double)win->h + (double)offset}}, true, true); +} + +void RGFW_window_focus(RGFW_window* win) { + RGFW_ASSERT(win); + objc_msgSend_void_bool((id)_RGFW->NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + ((void (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyWindow")); +} + +void RGFW_window_raise(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), (SEL)NULL); + objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGNormalWindowLevelKey); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen && (win->internal.flags & RGFW_windowFullscreen)) return; + if (!fullscreen && !(win->internal.flags & RGFW_windowFullscreen)) return; + + if (fullscreen) { + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + + win->internal.flags |= RGFW_windowFullscreen; + + RGFW_monitor* mon = RGFW_window_getMonitor(win); + RGFW_monitor_scaleToWindow(mon, win); + + RGFW_window_setBorder(win, RGFW_FALSE); + + if (mon != NULL) { + win->x = mon->x; + win->y = mon->y; + win->w = mon->mode.w; + win->h = mon->mode.h; + RGFW_window_resize(win, mon->mode.w, mon->mode.h); + RGFW_window_move(win, mon->x, mon->y); + } + + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), (SEL)NULL); + objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), 25); + } + + objc_msgSend_void_SEL(win->src.window, sel_registerName("toggleFullScreen:"), NULL); + + if (!fullscreen) { + win->x = win->internal.oldX; + win->y = win->internal.oldY; + win->w = win->internal.oldW; + win->h = win->internal.oldH; + win->internal.flags &= ~(u32)RGFW_windowFullscreen; + + RGFW_window_resize(win, win->w, win->h); + RGFW_window_move(win, win->x, win->y); + } +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if (RGFW_window_isMaximized(win)) return; + + win->internal.flags |= RGFW_windowMaximize; + objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); +} + +void RGFW_window_minimize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + objc_msgSend_void_SEL(win->src.window, sel_registerName("performMiniaturize:"), NULL); +} + +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + if (floating) objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGFloatingWindowLevelKey); + else objc_msgSend_void_id(win->src.window, sel_registerName("setLevel:"), kCGNormalWindowLevelKey); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + objc_msgSend_int(win->src.window, sel_registerName("setAlphaValue:"), opacity); + objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), (opacity < (u8)255)); + + if (opacity) + objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), NSColor_colorWithSRGB(0, 0, 0, opacity)); + +} + +void RGFW_window_restore(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + if (RGFW_window_isMaximized(win)) + objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); + + objc_msgSend_void_SEL(win->src.window, sel_registerName("deminiaturize:"), NULL); + RGFW_window_show(win); +} + +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + int level = ((int (*)(id, SEL))objc_msgSend) ((id)(win->src.window), (SEL)sel_registerName("level")); + return level > kCGNormalWindowLevelKey; +} + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + if (name == NULL) name = "\0"; + + id str = NSString_stringWithUTF8String(name); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setIgnoresMouseEvents:"), passthrough); +} +#endif + +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { + if (w == 0 && h == 0) { w = 1; h = 1; }; + + ((void (*)(id, SEL, NSSize))objc_msgSend) + ((id)win->src.window, sel_registerName("setContentAspectRatio:"), (NSSize){(CGFloat)w, (CGFloat)h}); +} + +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { + ((void (*)(id, SEL, NSSize))objc_msgSend) ((id)win->src.window, sel_registerName("setMinSize:"), (NSSize){(CGFloat)w, (CGFloat)h}); +} + +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { + if (w == 0 && h == 0) { + RGFW_monitor* mon = RGFW_window_getMonitor(win); + if (mon != NULL) { + w = mon->mode.w; + h = mon->mode.h; + } + } + + ((void (*)(id, SEL, NSSize))objc_msgSend) + ((id)win->src.window, sel_registerName("setMaxSize:"), (NSSize){(CGFloat)w, (CGFloat)h}); +} + +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(type); + + if (data == NULL) { + objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("setApplicationIconImage:"), NULL); + return RGFW_TRUE; + } + + id representation = NSBitmapImageRep_initWithBitmapData(NULL, w, h, 8, (NSInteger)4, true, false, "NSCalibratedRGBColorSpace", 1 << 1, w * 4, 32); + RGFW_copyImageData(NSBitmapImageRep_bitmapData(representation), w, h, RGFW_formatRGBA8, data, format); + + id dock_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){(CGFloat)w, (CGFloat)h})); + + objc_msgSend_void_id(dock_image, sel_registerName("addRepresentation:"), representation); + + objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("setApplicationIconImage:"), dock_image); + + NSRelease(dock_image); + NSRelease(representation); + + return RGFW_TRUE; +} + +id NSCursor_arrowStr(const char* str); +id NSCursor_arrowStr(const char* str) { + void* nclass = objc_getClass("NSCursor"); + SEL func = sel_registerName(str); + return (id) objc_msgSend_id(nclass, func); +} + +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { + if (data == NULL) { + objc_msgSend_void(NSCursor_arrowStr("arrowCursor"), sel_registerName("set")); + return NULL; + } + + id representation = (id)NSBitmapImageRep_initWithBitmapData(NULL, w, h, 8, (NSInteger)4, true, false, "NSCalibratedRGBColorSpace", 1 << 1, w * 4, 32); + RGFW_copyImageData(NSBitmapImageRep_bitmapData(representation), w, h, RGFW_formatRGBA8, data, format); + + id cursor_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){(CGFloat)w, (CGFloat)h})); + + objc_msgSend_void_id(cursor_image, sel_registerName("addRepresentation:"), representation); + + id cursor = (id) ((id(*)(id, SEL, id, NSPoint))objc_msgSend) + (NSAlloc(objc_getClass("NSCursor")), sel_registerName("initWithImage:hotSpot:"), cursor_image, (NSPoint){0.0, 0.0}); + + NSRelease(cursor_image); + NSRelease(representation); + + return (void*)cursor; +} + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { + RGFW_ASSERT(win != NULL); RGFW_ASSERT(mouse); + CGDisplayShowCursor(kCGDirectMainDisplay); + objc_msgSend_void((id)mouse, sel_registerName("set")); + win->src.mouse = mouse; +} + +void RGFW_freeMouse(RGFW_mouse* mouse) { + RGFW_ASSERT(mouse); + NSRelease((id)mouse); +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, show); + if (show) CGDisplayShowCursor(kCGDirectMainDisplay); + else CGDisplayHideCursor(kCGDirectMainDisplay); +} + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 stdMouse) { + const char* cursorSelectorStr; + switch (stdMouse) { + case RGFW_mouseNormal: cursorSelectorStr = "arrowCursor"; break; + case RGFW_mouseArrow: cursorSelectorStr = "arrowCursor"; break; + case RGFW_mouseIbeam: cursorSelectorStr = "IBeamCursor"; break; + case RGFW_mouseCrosshair: cursorSelectorStr = "crosshairCursor"; break; + case RGFW_mousePointingHand: cursorSelectorStr = "pointingHandCursor"; break; + case RGFW_mouseResizeEW: cursorSelectorStr = "resizeLeftRightCursor"; break; + case RGFW_mouseResizeE: cursorSelectorStr = "resizeLeftRightCursor"; break; + case RGFW_mouseResizeW: cursorSelectorStr = "resizeLeftRightCursor"; break; + case RGFW_mouseResizeNS: cursorSelectorStr = "resizeUpDownCursor"; break; + case RGFW_mouseResizeN: cursorSelectorStr = "resizeUpDownCursor"; break; + case RGFW_mouseResizeS: cursorSelectorStr = "resizeUpDownCursor"; break; + case RGFW_mouseResizeNWSE: cursorSelectorStr = "_windowResizeNorthWestSouthEastCursor"; break; + case RGFW_mouseResizeNW: cursorSelectorStr = "_windowResizeNorthWestSouthEastCursor"; break; + case RGFW_mouseResizeSE: cursorSelectorStr = "_windowResizeNorthWestSouthEastCursor"; break; + case RGFW_mouseResizeNESW: cursorSelectorStr = "_windowResizeNorthEastSouthWestCursor"; break; + case RGFW_mouseResizeNE: cursorSelectorStr = "_windowResizeNorthEastSouthWestCursor"; break; + case RGFW_mouseResizeSW: cursorSelectorStr = "_windowResizeNorthEastSouthWestCursor"; break; + case RGFW_mouseResizeAll: cursorSelectorStr = "openHandCursor"; break; + case RGFW_mouseNotAllowed: cursorSelectorStr = "operationNotAllowedCursor"; break; + case RGFW_mouseWait: cursorSelectorStr = "arrowCursor"; break; + case RGFW_mouseProgress: cursorSelectorStr = "arrowCursor"; break; + default: + return RGFW_FALSE; + } + + id mouse = NSCursor_arrowStr(cursorSelectorStr); + + if (mouse == NULL) + return RGFW_FALSE; + + RGFW_UNUSED(win); + CGDisplayShowCursor(kCGDirectMainDisplay); + objc_msgSend_void(mouse, sel_registerName("set")); + win->src.mouse = mouse; + + return RGFW_TRUE; +} + +void RGFW_window_setRawMouseModePlatform(RGFW_window* win, RGFW_bool state) { + RGFW_UNUSED(win); RGFW_UNUSED(state); +} + +void RGFW_window_captureMousePlatform(RGFW_window* win, RGFW_bool state) { + RGFW_UNUSED(win); + CGAssociateMouseAndMouseCursorPosition(!(state == RGFW_TRUE)); +} + +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { + RGFW_UNUSED(win); + + win->internal.lastMouseX = x - win->x; + win->internal.lastMouseY = y - win->y; + CGWarpMouseCursorPosition((CGPoint){(CGFloat)x, (CGFloat)y}); +} + + +void RGFW_window_hide(RGFW_window* win) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), false); +} + +void RGFW_window_show(RGFW_window* win) { + if (win->internal.flags & RGFW_windowFocusOnShow) + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); + + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), NULL); + objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), true); +} + +void RGFW_window_flash(RGFW_window* win, RGFW_flashRequest request) { + if (RGFW_window_isInFocus(win) && request) { + return; + } + + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + + if (_RGFW->flash) { + ((void (*)(id, SEL, NSInteger))objc_msgSend) ((id)_RGFW->NSApp, sel_registerName("cancelUserAttentionRequest:"), _RGFW->flash); + } + + switch (request) { + case RGFW_flashBriefly: + _RGFW->flash = ((NSInteger (*)(id, SEL, NSInteger))objc_msgSend) ((id)_RGFW->NSApp, sel_registerName("requestUserAttention:"), NSInformationalRequest); + break; + case RGFW_flashUntilFocused: + _RGFW->flash = ((NSInteger (*)(id, SEL, NSInteger))objc_msgSend) ((id)_RGFW->NSApp, sel_registerName("requestUserAttention:"), NSCriticalRequest); + break; + default: break; + } + + objc_msgSend_bool_void(pool, sel_registerName("drain")); +} + +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + bool visible = objc_msgSend_bool(win->src.window, sel_registerName("isVisible")); + return visible == NO && !RGFW_window_isMinimized(win); +} + +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + return objc_msgSend_bool(win->src.window, sel_registerName("isMiniaturized")) == YES; +} + +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_bool b = (RGFW_bool)objc_msgSend_bool(win->src.window, sel_registerName("isZoomed")); + return b; +} + +RGFWDEF id RGFW_getNSScreenForDisplayUInt(u32 uintNum); +id RGFW_getNSScreenForDisplayUInt(u32 uintNum) { + Class NSScreenClass = objc_getClass("NSScreen"); + + id screens = objc_msgSend_id(NSScreenClass, sel_registerName("screens")); + + NSUInteger count = (NSUInteger)objc_msgSend_uint(screens, sel_registerName("count")); + NSUInteger i; + for (i = 0; i < count; i++) { + id screen = ((id (*)(id, SEL, int))objc_msgSend) (screens, sel_registerName("objectAtIndex:"), (int)i); + id description = objc_msgSend_id(screen, sel_registerName("deviceDescription")); + id screenNumberKey = NSString_stringWithUTF8String("NSScreenNumber"); + id screenNumber = objc_msgSend_id_id(description, sel_registerName("objectForKey:"), screenNumberKey); + + if (CGDisplayUnitNumber((CGDirectDisplayID)objc_msgSend_uint(screenNumber, sel_registerName("unsignedIntValue"))) == uintNum) { + return screen; + } + } + + return NULL; +} + +float RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode); +float RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { + if (mode) { + float refreshRate = (float)CGDisplayModeGetRefreshRate(mode); + if (refreshRate != 0) return refreshRate; + } + +#ifndef RGFW_NO_IOKIT + float res = RGFW_osx_getFallbackRefreshRate(display); + if (res != 0) return res; +#else + RGFW_UNUSED(display); +#endif + return 60; +} + +void RGFW_pollMonitors(void) { + static CGDirectDisplayID displays[RGFW_MAX_MONITORS]; + u32 count; + + if (CGGetActiveDisplayList(RGFW_MAX_MONITORS, displays, &count) != kCGErrorSuccess) { + return; + } + + if (count > RGFW_MAX_MONITORS) count = RGFW_MAX_MONITORS; + + for (RGFW_monitorNode* node = _RGFW->monitors.list.head; node; node = node->next) { + node->disconnected = RGFW_TRUE; + } + + CGDirectDisplayID primary = CGMainDisplayID(); + + u32 i; + for (i = 0; i < count; i++) { + RGFW_monitor monitor; + + u32 uintNum = CGDisplayUnitNumber(displays[i]); + id screen = RGFW_getNSScreenForDisplayUInt(uintNum); + + RGFW_monitorNode* node; + for (node = _RGFW->monitors.list.head; node; node = node->next) { + if (node->uintNum == uintNum) break; + } + + if (node) { + node->screen = (void*)screen; + node->display = displays[i]; + node->disconnected = RGFW_FALSE; + if (displays[i] == primary) { + _RGFW->monitors.primary = node; + } + continue; + } + + const char name[] = "MacOS\0"; + RGFW_MEMCPY(monitor.name, name, 6); + + CGRect bounds = CGDisplayBounds(displays[i]); + monitor.x = (i32)bounds.origin.x; + monitor.y = (i32)RGFW_cocoaYTransform((float)(bounds.origin.y + bounds.size.height - 1)); + + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]); + monitor.mode.w = (i32)CGDisplayModeGetWidth(mode); + monitor.mode.h = (i32)CGDisplayModeGetHeight(mode); + monitor.mode.src = (void*)mode; + monitor.mode.red = 8; monitor.mode.green = 8; monitor.mode.blue = 8; + + monitor.mode.refreshRate = RGFW_osx_getRefreshRate(displays[i], mode); + CFRelease(mode); + + CGSize screenSizeMM = CGDisplayScreenSize(displays[i]); + monitor.physW = (float)screenSizeMM.width / 25.4f; + monitor.physH = (float)screenSizeMM.height / 25.4f; + + float ppi_width = ((float)monitor.mode.w / monitor.physW); + float ppi_height = ((float)monitor.mode.h / monitor.physH); + + monitor.pixelRatio = (float)((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret) (screen, sel_registerName("backingScaleFactor")); + float dpi = 96.0f * monitor.pixelRatio; + + monitor.scaleX = ((((float) (ppi_width) / dpi) * 10.0f)) / 10.0f; + monitor.scaleY = ((((float) (ppi_height) / dpi) * 10.0f)) / 10.0f; + + node = RGFW_monitors_add(&monitor); + + node->screen = (void*)screen; + node->uintNum = uintNum; + node->display = displays[i]; + + if (displays[i] == primary) { + _RGFW->monitors.primary = node; + } + + RGFW_monitorCallback(_RGFW->root, &node->mon, RGFW_TRUE); + } + + RGFW_monitors_refresh(); +} + +RGFW_bool RGFW_monitor_getWorkarea(RGFW_monitor* monitor, i32* x, i32* y, i32* width, i32* height) { + NSRect frameRect = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)monitor->node->screen, sel_registerName("visibleFrame")); + + if (x) *x = (i32)frameRect.origin.x; + if (y) *y = (i32)RGFW_cocoaYTransform((float)(frameRect.origin.y + frameRect.size.height - (double)1.0f)); + if (width) *width = (i32)frameRect.size.width; + if (height) *height = (i32)frameRect.size.height; + + return RGFW_TRUE; +} + +size_t RGFW_monitor_getGammaRampPtr(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + + uint32_t size = CGDisplayGammaTableCapacity(monitor->node->display); + CGGammaValue* values = (CGGammaValue*)RGFW_ALLOC(size * 3 * sizeof(CGGammaValue)); + + CGGetDisplayTransferByTable(monitor->node->display, size, values, values + size, values + size * 2, &size); + + for (u32 i = 0; ramp && i < size; i++) { + ramp->red[i] = (u16) (values[i] * 65535); + ramp->green[i] = (u16) (values[i + size] * 65535); + ramp->blue[i] = (u16) (values[i + size * 2] * 65535); + } + + RGFW_FREE(values); + + objc_msgSend_bool_void(pool, sel_registerName("drain")); + return size; +} + +RGFW_bool RGFW_monitor_setGammaRamp(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + + CGGammaValue* values = (CGGammaValue*)RGFW_ALLOC(ramp->count * 3 * sizeof(CGGammaValue)); + + for (u32 i = 0; i < ramp->count; i++) { + values[i] = ramp->red[i] / 65535.f; + values[i + ramp->count] = ramp->green[i] / 65535.f; + values[i + ramp->count * 2] = ramp->blue[i] / 65535.f; + } + + CGSetDisplayTransferByTable(monitor->node->display, (u32)ramp->count, values, values + ramp->count, values + ramp->count * 2); + + RGFW_FREE(values); + + objc_msgSend_bool_void(pool, sel_registerName("drain")); + + return RGFW_TRUE; +} + +size_t RGFW_monitor_getModesPtr(RGFW_monitor* mon, RGFW_monitorMode** modes) { + CGDirectDisplayID display = mon->node->display; + CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); + + if (allModes == NULL) { + return RGFW_FALSE; + } + + size_t count = (size_t)CFArrayGetCount(allModes); + + CFIndex i; + for (i = 0; i < (CFIndex)count && modes; i++) { + CGDisplayModeRef cmode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); + + RGFW_monitorMode foundMode; + foundMode.w = (i32)CGDisplayModeGetWidth(cmode); + foundMode.h = (i32)CGDisplayModeGetHeight(cmode); + foundMode.refreshRate = RGFW_osx_getRefreshRate(display, cmode); + foundMode.red = 8; foundMode.green = 8; foundMode.blue = 8; + foundMode.src = (void*)cmode; + (*modes)[i] = foundMode; + } + + CFRelease(allModes); + return count; +} + +RGFW_bool RGFW_monitor_setMode(RGFW_monitor* mon, RGFW_monitorMode* mode) { + if (CGDisplaySetDisplayMode(mon->node->display, (CGDisplayModeRef)mode->src, NULL) == kCGErrorSuccess) { + return RGFW_TRUE; + } + + return RGFW_FALSE; +} + +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request) { + CGDirectDisplayID display = mon->node->display; + CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); + + if (allModes == NULL) { + return RGFW_FALSE; + } + + CGDisplayModeRef native = NULL; + + CFIndex i; + for (i = 0; i < CFArrayGetCount(allModes); i++) { + CGDisplayModeRef cmode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); + + RGFW_monitorMode foundMode; + foundMode.w = (i32)CGDisplayModeGetWidth(cmode); + foundMode.h = (i32)CGDisplayModeGetHeight(cmode); + foundMode.refreshRate = RGFW_osx_getRefreshRate(display, cmode); + foundMode.red = 8; foundMode.green = 8; foundMode.blue = 8; + foundMode.src = (void*)cmode; + + if (RGFW_monitorModeCompare(mode, &foundMode, request)) { + native = cmode; + mon->mode = foundMode; + break; + } + } + + CFRelease(allModes); + + if (native) { + if (CGDisplaySetDisplayMode(display, native, NULL) == kCGErrorSuccess) { + return RGFW_TRUE; + } + } + + return RGFW_FALSE; +} + +RGFW_monitor* RGFW_window_getMonitor(RGFW_window* win) { + id screen = objc_msgSend_id(win->src.window, sel_registerName("screen")); + id description = objc_msgSend_id(screen, sel_registerName("deviceDescription")); + id screenNumberKey = NSString_stringWithUTF8String("NSScreenNumber"); + id screenNumber = objc_msgSend_id_id(description, sel_registerName("objectForKey:"), screenNumberKey); + + CGDirectDisplayID display = (CGDirectDisplayID)objc_msgSend_uint(screenNumber, sel_registerName("unsignedIntValue")); + + RGFW_monitorNode* node = _RGFW->monitors.list.head; + for (node = _RGFW->monitors.list.head; node; node = node->next) { + if (node->display == display && (id)node->screen == screen) { + break; + } + } + + return &node->mon; +} + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + size_t clip_len; + char* clip = (char*)NSPasteboard_stringForType(NSPasteboard_generalPasteboard(), NSPasteboardTypeString, &clip_len); + if (clip == NULL) return -1; + + if (str != NULL) { + if (strCapacity < clip_len) + return 0; + + RGFW_MEMCPY(str, clip, clip_len); + + str[clip_len] = '\0'; + } + + return (RGFW_ssize_t)clip_len; +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_UNUSED(textLen); + + NSPasteboardType array[] = { NSPasteboardTypeString, NULL }; + NSPasteBoard_declareTypes(NSPasteboard_generalPasteboard(), array, 1, NULL); + + SEL func = sel_registerName("setString:forType:"); + ((bool (*)(id, SEL, id, id))objc_msgSend) + (NSPasteboard_generalPasteboard(), func, NSString_stringWithUTF8String(text), NSString_stringWithUTF8String((const char*)NSPasteboardTypeString)); +} + +#ifdef RGFW_OPENGL +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param); +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param) { + ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) + (context, sel_registerName("setValues:forParameter:"), vals, param); +} + + +/* MacOS OpenGL API spares us yet again (there are no extensions) */ +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char * extension, size_t len) { RGFW_UNUSED(extension); RGFW_UNUSED(len); return RGFW_FALSE; } + +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { + static CFBundleRef RGFWnsglFramework = NULL; + if (RGFWnsglFramework == NULL) + RGFWnsglFramework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + + CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); + + RGFW_proc symbol = (RGFW_proc)CFBundleGetFunctionPointerForName(RGFWnsglFramework, symbolName); + + CFRelease(symbolName); + + return symbol; +} + +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + win->src.ctx.native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + + i32 attribs[40]; + size_t render_type_index = 0; + { + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, attribs, 40); + + i32 colorBits = (i32)(hints->red + hints->green + hints->blue + hints->alpha) / 4; + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAColorSize, colorBits); + + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAlphaSize, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFADepthSize, hints->depth); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAStencilSize, hints->stencil); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAuxBuffers, hints->auxBuffers); + RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAClosestPolicy); + if (hints->samples) { + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASampleBuffers, 1); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASamples, hints->samples); + } else RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASampleBuffers, 0); + + if (hints->doubleBuffer) + RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFADoubleBuffer); + + #ifdef RGFW_COCOA_GRAPHICS_SWITCHING + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAllowOfflineRenderers, kCGLPFASupportsAutomaticGraphicsSwitching) + #endif + #if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 + if (hints->stereo]) RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAStereo); + #endif + + /* macOS has the surface attribs and the OpenGL attribs connected for some reason maybe this is to give macOS more control to limit openGL/the OpenGL version? */ + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAOpenGLProfile, + (hints->major >= 4) ? NSOpenGLProfileVersion4_1Core : (hints->major >= 3) ? + NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy); + + if (hints->major <= 2) { + i32 accumSize = (i32)(hints->accumRed + hints->accumGreen + hints->accumBlue + hints->accumAlpha) / 4; + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAccumSize, accumSize); + } + + if (hints->renderer == RGFW_glSoftware) { + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFARendererID, kCGLRendererGenericFloatID); + } else { + RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAAccelerated); + } + render_type_index = stack.count - 1; + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + } + + void* format = (void*) ((id(*)(id, SEL, const u32*))objc_msgSend) (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), (u32*)attribs); + if (format == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load pixel format for OpenGL"); + + assert(render_type_index + 3 < (sizeof(attribs) / sizeof(attribs[0]))); + attribs[render_type_index] = NSOpenGLPFARendererID; + attribs[render_type_index + 1] = kCGLRendererGenericFloatID; + attribs[render_type_index + 3] = 0; + + format = (void*) ((id(*)(id, SEL, const u32*))objc_msgSend) (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), (u32*)attribs); + if (format == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "and loading software rendering OpenGL failed"); + else + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Switching to software rendering"); + } + + /* the pixel format can be passed directly to OpenGL context creation to create a context + this is because the format also includes information about the OpenGL version (which may be a bad thing) */ + + if (win->src.view) + NSRelease(win->src.view); + win->src.view = (id) ((id(*)(id, SEL, NSRect, u32*))objc_msgSend) (NSAlloc(_RGFW->customViewClasses[1]), + sel_registerName("initWithFrame:pixelFormat:"), (NSRect){{0, 0}, {(double)win->w, (double)win->h}}, (u32*)format); + + id share = NULL; + if (hints->share) { + share = (id)hints->share->ctx; + } + + win->src.ctx.native->ctx = ((id (*)(id, SEL, id, id))objc_msgSend)(NSAlloc(objc_getClass("NSOpenGLContext")), + sel_registerName("initWithFormat:shareContext:"), + (id)format, share); + + win->src.ctx.native->format = format; + + objc_msgSend_void_id(win->src.view, sel_registerName("setOpenGLContext:"), win->src.ctx.native->ctx); + if (win->internal.flags & RGFW_windowTransparent) { + i32 opacity = 0; + #define NSOpenGLCPSurfaceOpacity 236 + NSOpenGLContext_setValues((id)win->src.ctx.native->ctx, &opacity, (NSOpenGLContextParameter)NSOpenGLCPSurfaceOpacity); + + } + + objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("makeCurrentContext")); + + objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); + objc_msgSend_void_bool(win->src.view, sel_registerName("setWantsLayer:"), true); + objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); + + RGFW_window_swapInterval_OpenGL(win, 0); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + return RGFW_TRUE; +} + +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + objc_msgSend_void(ctx->format, sel_registerName("release")); + win->src.ctx.native->format = NULL; + + objc_msgSend_void(ctx->ctx, sel_registerName("release")); + win->src.ctx.native->ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win) RGFW_ASSERT(win->src.ctx.native); + if (win != NULL) + objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("makeCurrentContext")); + else + objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("clearCurrentContext")); +} +void* RGFW_getCurrentContext_OpenGL(void) { + return objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("currentContext")); +} + +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + RGFW_ASSERT(win && win->src.ctx.native); + objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("flushBuffer")); +} +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL && win->src.ctx.native != NULL); + NSOpenGLContext_setValues((id)win->src.ctx.native->ctx, &swapInterval, (NSOpenGLContextParameter)222); +} +#endif + +void RGFW_deinitPlatform(void) { + objc_msgSend_void_id(_RGFW->NSApp, sel_registerName("setDelegate:"), NULL); + + NSRelease(_RGFW->NSApp); + NSRelease(_RGFW->customNSAppDelegate); + + _RGFW->customNSAppDelegate = NULL; + + objc_disposeClassPair((Class)_RGFW->customViewClasses[0]); + objc_disposeClassPair((Class)_RGFW->customViewClasses[1]); + objc_disposeClassPair((Class)_RGFW->customWindowDelegateClass); + objc_disposeClassPair((Class)_RGFW->customNSAppDelegateClass); +} + +void RGFW_window_closePlatform(RGFW_window* win) { + objc_msgSend_void_id((id)win->src.window, sel_registerName("setDelegate:"), NULL); + NSRelease((id)win->src.delegate); + NSRelease(win->src.view); + + objc_msgSend_id(win->src.window, sel_registerName("close")); + NSRelease(win->src.window); +} + +#ifdef RGFW_VULKAN +VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { + RGFW_ASSERT(win != NULL); RGFW_ASSERT(instance); + RGFW_ASSERT(surface != NULL); + + *surface = VK_NULL_HANDLE; + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + + id nsView = (id)win->src.view; + if (!nsView) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errMetal, "NSView is NULL for macOS window"); + return -1; + } + + + id layer = ((id (*)(id, SEL))objc_msgSend)(nsView, sel_registerName("layer")); + + void* metalLayer = RGFW_getLayer_OSX(); + if (metalLayer == NULL) { + return -1; + } + ((void (*)(id, SEL, id))objc_msgSend)((id)nsView, sel_registerName("setLayer:"), metalLayer); + ((void (*)(id, SEL, BOOL))objc_msgSend)(nsView, sel_registerName("setWantsLayer:"), YES); + + VkResult result; +/* + VkMetalSurfaceCreateInfoEXT macos; + macos.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + macos.slayer = metalLayer; + RGFW_MEMSET(&macos, 0, sizeof(macos)); + result = vkCreateMacOSSurfaceMVK(instance, &macos, NULL, surface); +*/ + + VkMacOSSurfaceCreateInfoMVK macos; + RGFW_MEMSET(&macos, 0, sizeof(macos)); + macos.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + macos.pView = nsView; + + result = vkCreateMacOSSurfaceMVK(instance, &macos, NULL, surface); + + objc_msgSend_bool_void(pool, sel_registerName("drain")); + + return result; +} +#endif + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + id* nsView = (id*)window->src.view; + if (!nsView) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errMetal, "NSView is NULL for macOS window"); + return NULL; + } + + ((void (*)(id, SEL, BOOL))objc_msgSend)(nsView, sel_registerName("setWantsLayer:"), YES); + id layer = ((id (*)(id, SEL))objc_msgSend)(nsView, sel_registerName("layer")); + + void* metalLayer = RGFW_getLayer_OSX(); + if (metalLayer == NULL) { + return NULL; + } + ((void (*)(id, SEL, id))objc_msgSend)((id)nsView, sel_registerName("setLayer:"), metalLayer); + layer = metalLayer; + + WGPUSurfaceSourceMetalLayer fromMetal = {0}; + fromMetal.chain.sType = WGPUSType_SurfaceSourceMetalLayer; +#ifdef __OBJC__ + fromMetal.layer = (__bridge CAMetalLayer*)layer; /* Use __bridge for ARC compatibility if mixing C/Obj-C */ +#else + fromMetal.layer = layer; +#endif + + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromMetal.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + +#endif /* RGFW_MACOS */ + +/* + End of MaOS defines +*/ + +/* + WASM defines +*/ + +#ifdef RGFW_WASM +EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + RGFW_windowResizedCallback(_RGFW->root, E->windowInnerWidth, E->windowInnerHeight); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + if (!(_RGFW->root->internal.enabledEvents & RGFW_windowResizedFlag)) return EM_TRUE; + + static u8 fullscreen = RGFW_FALSE; + static i32 originalW, originalH; + + if (fullscreen == RGFW_FALSE) { + originalW = _RGFW->root->w; + originalH = _RGFW->root->h; + } + + fullscreen = !fullscreen; + _RGFW->root->w = E->screenWidth; + _RGFW->root->h = E->screenHeight; + + EM_ASM("Module.canvas.focus();"); + + if (fullscreen == RGFW_FALSE) { + _RGFW->root->w = originalW; + _RGFW->root->h = originalH; + } else { + #if __EMSCRIPTEN_major__ >= 1 && __EMSCRIPTEN_minor__ >= 29 && __EMSCRIPTEN_tiny__ >= 0 + EmscriptenFullscreenStrategy FSStrat = {0}; + FSStrat.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; + FSStrat.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; + FSStrat.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; + emscripten_request_fullscreen_strategy("#canvas", 1, &FSStrat); + #else + emscripten_request_fullscreen("#canvas", 1); + #endif + } + + emscripten_set_canvas_element_size("#canvas", _RGFW->root->w, _RGFW->root->h); + RGFW_windowResizedCallback(_RGFW->root, _RGFW->root->w, _RGFW->root->h); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); + + RGFW_focusCallback(_RGFW->root, 1); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); + + RGFW_focusCallback(_RGFW->root, 0); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + RGFW_mousePosCallback(_RGFW->root, E->targetX, E->targetY, E->movementX, E->movementY); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + int button = E->button; + if (button > 2) + button += 2; + + RGFW_mouseButtonCallback(_RGFW->root, button, 1); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + int button = E->button; + if (button > 2) + button += 2; + + RGFW_mouseButtonCallback(_RGFW->root, button, 0); + return EM_TRUE; +} + +EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_mouseScrollCallback(_RGFW->root, E->deltaX, E->deltaY); + + return EM_TRUE; +} + +EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return EM_TRUE; + + size_t i; + for (i = 0; i < (size_t)E->numTouches; i++) { + RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); + RGFW_mouseButtonCallback(_RGFW->root, RGFW_mouseLeft, 1); + } + + return EM_TRUE; +} + +EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + if (!(_RGFW->root->internal.enabledEvents & RGFW_mousePosChangedFlag)) return EM_TRUE; + + size_t i; + for (i = 0; i < (size_t)E->numTouches; i++) { + RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); + } + return EM_TRUE; +} + +EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* E, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return EM_TRUE; + + size_t i; + for (i = 0; i < (size_t)E->numTouches; i++) { + RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); + RGFW_mouseButtonCallback(_RGFW->root, RGFW_mouseLeft, 0); + } + return EM_TRUE; +} + +EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); return EM_TRUE; } + +u32 RGFW_WASMPhysicalToRGFW(u32 hash); + +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool press) { + const char* iCode = code; + + u32 hash = 0; + while(*iCode) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*iCode++; + + u32 physicalKey = RGFW_WASMPhysicalToRGFW(hash); + + u8 mappedKey = (u8)(*((u32*)key)); + + if (*((u16*)key) != mappedKey) { + mappedKey = 0; + if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; + } + + RGFW_keyCallback(_RGFW->root, physicalKey, mappedKey, _RGFW->root->internal.mod, RGFW_window_isKeyDown(_RGFW->root, (u8)physicalKey), press); +} + +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyMods(RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { + RGFW_updateKeyModsEx(_RGFW->root, capital, numlock, control, alt, shift, super, scroll); +} + +void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { + RGFW_dataDropCallback(_RGFW->root, _RGFW->files, count); +} + +void RGFW_stopCheckEvents(void) { + _RGFW->stopCheckEvents_bool = RGFW_TRUE; +} + +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + return RGFW_TRUE; +} + +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { + /* TODO: Needs fixing. */ + RGFW_copyImageData(surface->data, surface->w, RGFW_MIN(win->h, surface->h), RGFW_formatRGBA8, surface->data, surface->format); + EM_ASM_({ + var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); + let context = document.getElementById("canvas").getContext("2d"); + let image = context.getImageData(0, 0, $1, $2); + image.data.set(data); + context.putImageData(image, 0, $4 - $2); + }, surface->data, surface->w, surface->h, RGFW_MIN(win->h, surface->w), RGFW_MIN(win->h, surface->h)); +} + +void RGFW_surface_freePtr(RGFW_surface* surface) { } + +void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { + /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ + /* TODO: find a better way to do this + */ + RGFW_STRNCPY((char*)_RGFW->files[index], file, RGFW_MAX_PATH - 1); + _RGFW->files[index][RGFW_MAX_PATH - 1] = '\0'; +} + +#include +#include +#include +#include + +void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } + +void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { + FILE* file = fopen(path, "w+"); + if (file == NULL) + return; + + fwrite(data, sizeof(char), len, file); + fclose(file); +} + +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[DOM_VK_BACK_QUOTE] = RGFW_backtick; + _RGFW->keycodes[DOM_VK_0] = RGFW_0; + _RGFW->keycodes[DOM_VK_1] = RGFW_1; + _RGFW->keycodes[DOM_VK_2] = RGFW_2; + _RGFW->keycodes[DOM_VK_3] = RGFW_3; + _RGFW->keycodes[DOM_VK_4] = RGFW_4; + _RGFW->keycodes[DOM_VK_5] = RGFW_5; + _RGFW->keycodes[DOM_VK_6] = RGFW_6; + _RGFW->keycodes[DOM_VK_7] = RGFW_7; + _RGFW->keycodes[DOM_VK_8] = RGFW_8; + _RGFW->keycodes[DOM_VK_9] = RGFW_9; + _RGFW->keycodes[DOM_VK_SPACE] = RGFW_space; + _RGFW->keycodes[DOM_VK_A] = RGFW_a; + _RGFW->keycodes[DOM_VK_B] = RGFW_b; + _RGFW->keycodes[DOM_VK_C] = RGFW_c; + _RGFW->keycodes[DOM_VK_D] = RGFW_d; + _RGFW->keycodes[DOM_VK_E] = RGFW_e; + _RGFW->keycodes[DOM_VK_F] = RGFW_f; + _RGFW->keycodes[DOM_VK_G] = RGFW_g; + _RGFW->keycodes[DOM_VK_H] = RGFW_h; + _RGFW->keycodes[DOM_VK_I] = RGFW_i; + _RGFW->keycodes[DOM_VK_J] = RGFW_j; + _RGFW->keycodes[DOM_VK_K] = RGFW_k; + _RGFW->keycodes[DOM_VK_L] = RGFW_l; + _RGFW->keycodes[DOM_VK_M] = RGFW_m; + _RGFW->keycodes[DOM_VK_N] = RGFW_n; + _RGFW->keycodes[DOM_VK_O] = RGFW_o; + _RGFW->keycodes[DOM_VK_P] = RGFW_p; + _RGFW->keycodes[DOM_VK_Q] = RGFW_q; + _RGFW->keycodes[DOM_VK_R] = RGFW_r; + _RGFW->keycodes[DOM_VK_S] = RGFW_s; + _RGFW->keycodes[DOM_VK_T] = RGFW_t; + _RGFW->keycodes[DOM_VK_U] = RGFW_u; + _RGFW->keycodes[DOM_VK_V] = RGFW_v; + _RGFW->keycodes[DOM_VK_W] = RGFW_w; + _RGFW->keycodes[DOM_VK_X] = RGFW_x; + _RGFW->keycodes[DOM_VK_Y] = RGFW_y; + _RGFW->keycodes[DOM_VK_Z] = RGFW_z; + _RGFW->keycodes[DOM_VK_PERIOD] = RGFW_period; + _RGFW->keycodes[DOM_VK_COMMA] = RGFW_comma; + _RGFW->keycodes[DOM_VK_SLASH] = RGFW_slash; + _RGFW->keycodes[DOM_VK_OPEN_BRACKET] = RGFW_bracket; + _RGFW->keycodes[DOM_VK_CLOSE_BRACKET] = RGFW_closeBracket; + _RGFW->keycodes[DOM_VK_SEMICOLON] = RGFW_semicolon; + _RGFW->keycodes[DOM_VK_QUOTE] = RGFW_apostrophe; + _RGFW->keycodes[DOM_VK_BACK_SLASH] = RGFW_backSlash; + _RGFW->keycodes[DOM_VK_RETURN] = RGFW_return; + _RGFW->keycodes[DOM_VK_DELETE] = RGFW_delete; + _RGFW->keycodes[DOM_VK_NUM_LOCK] = RGFW_numLock; + _RGFW->keycodes[DOM_VK_DIVIDE] = RGFW_kpSlash; + _RGFW->keycodes[DOM_VK_MULTIPLY] = RGFW_kpMultiply; + _RGFW->keycodes[DOM_VK_SUBTRACT] = RGFW_kpMinus; + _RGFW->keycodes[DOM_VK_NUMPAD1] = RGFW_kp1; + _RGFW->keycodes[DOM_VK_NUMPAD2] = RGFW_kp2; + _RGFW->keycodes[DOM_VK_NUMPAD3] = RGFW_kp3; + _RGFW->keycodes[DOM_VK_NUMPAD4] = RGFW_kp4; + _RGFW->keycodes[DOM_VK_NUMPAD5] = RGFW_kp5; + _RGFW->keycodes[DOM_VK_NUMPAD6] = RGFW_kp6; + _RGFW->keycodes[DOM_VK_NUMPAD9] = RGFW_kp9; + _RGFW->keycodes[DOM_VK_NUMPAD0] = RGFW_kp0; + _RGFW->keycodes[DOM_VK_DECIMAL] = RGFW_kpPeriod; + _RGFW->keycodes[DOM_VK_RETURN] = RGFW_kpReturn; + _RGFW->keycodes[DOM_VK_HYPHEN_MINUS] = RGFW_minus; + _RGFW->keycodes[DOM_VK_EQUALS] = RGFW_equals; + _RGFW->keycodes[DOM_VK_BACK_SPACE] = RGFW_backSpace; + _RGFW->keycodes[DOM_VK_TAB] = RGFW_tab; + _RGFW->keycodes[DOM_VK_CAPS_LOCK] = RGFW_capsLock; + _RGFW->keycodes[DOM_VK_SHIFT] = RGFW_shiftL; + _RGFW->keycodes[DOM_VK_CONTROL] = RGFW_controlL; + _RGFW->keycodes[DOM_VK_ALT] = RGFW_altL; + _RGFW->keycodes[DOM_VK_META] = RGFW_superL; + _RGFW->keycodes[DOM_VK_F1] = RGFW_F1; + _RGFW->keycodes[DOM_VK_F2] = RGFW_F2; + _RGFW->keycodes[DOM_VK_F3] = RGFW_F3; + _RGFW->keycodes[DOM_VK_F4] = RGFW_F4; + _RGFW->keycodes[DOM_VK_F5] = RGFW_F5; + _RGFW->keycodes[DOM_VK_F6] = RGFW_F6; + _RGFW->keycodes[DOM_VK_F7] = RGFW_F7; + _RGFW->keycodes[DOM_VK_F8] = RGFW_F8; + _RGFW->keycodes[DOM_VK_F9] = RGFW_F9; + _RGFW->keycodes[DOM_VK_F10] = RGFW_F10; + _RGFW->keycodes[DOM_VK_F11] = RGFW_F11; + _RGFW->keycodes[DOM_VK_F12] = RGFW_F12; + _RGFW->keycodes[DOM_VK_UP] = RGFW_up; + _RGFW->keycodes[DOM_VK_DOWN] = RGFW_down; + _RGFW->keycodes[DOM_VK_LEFT] = RGFW_left; + _RGFW->keycodes[DOM_VK_RIGHT] = RGFW_right; + _RGFW->keycodes[DOM_VK_INSERT] = RGFW_insert; + _RGFW->keycodes[DOM_VK_END] = RGFW_end; + _RGFW->keycodes[DOM_VK_PAGE_UP] = RGFW_pageUp; + _RGFW->keycodes[DOM_VK_PAGE_DOWN] = RGFW_pageDown; + _RGFW->keycodes[DOM_VK_ESCAPE] = RGFW_escape; + _RGFW->keycodes[DOM_VK_HOME] = RGFW_home; + _RGFW->keycodes[DOM_VK_SCROLL_LOCK] = RGFW_scrollLock; + _RGFW->keycodes[DOM_VK_PRINTSCREEN] = RGFW_printScreen; + _RGFW->keycodes[DOM_VK_PAUSE] = RGFW_pause; + _RGFW->keycodes[DOM_VK_F13] = RGFW_F13; + _RGFW->keycodes[DOM_VK_F14] = RGFW_F14; + _RGFW->keycodes[DOM_VK_F15] = RGFW_F15; + _RGFW->keycodes[DOM_VK_F16] = RGFW_F16; + _RGFW->keycodes[DOM_VK_F17] = RGFW_F17; + _RGFW->keycodes[DOM_VK_F18] = RGFW_F18; + _RGFW->keycodes[DOM_VK_F19] = RGFW_F19; + _RGFW->keycodes[DOM_VK_F20] = RGFW_F20; + _RGFW->keycodes[DOM_VK_F21] = RGFW_F21; + _RGFW->keycodes[DOM_VK_F22] = RGFW_F22; + _RGFW->keycodes[DOM_VK_F23] = RGFW_F23; + _RGFW->keycodes[DOM_VK_F24] = RGFW_F24; +} + +i32 RGFW_initPlatform(void) { return 0; } + +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { + emscripten_set_canvas_element_size("#canvas", win->w, win->h); + emscripten_set_window_title(name); + + /* load callbacks */ + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); + emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); + emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); + emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); + emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); + emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); + emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); + emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); + emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); + emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); + emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); + + if (flags & RGFW_windowAllowDND) { + win->internal.flags |= RGFW_windowAllowDND; + } + + EM_ASM({ + window.addEventListener("keydown", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 1); + _free(key); _free(code); + }, + true); + window.addEventListener("keyup", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 0); + _free(key); _free(code); + }, + true); + }); + + EM_ASM({ + var canvas = document.getElementById('canvas'); + canvas.addEventListener('drop', function(e) { + e.preventDefault(); + if (e.dataTransfer.file < 0) + return; + + var filenamesArray = []; + var count = e.dataTransfer.files.length; + + /* Read and save the files to emscripten's files */ + var drop_dir = '.rgfw_dropped_files'; + Module._RGFW_mkdir(drop_dir); + + for (var i = 0; i < count; i++) { + var file = e.dataTransfer.files[i]; + + var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); + var reader = new FileReader(); + + reader.onloadend = (e) => { + if (reader.readyState != 2) { + out('failed to read dropped file: '+file.name+': '+reader.error); + } + else { + var data = e.target.result; + + Module._RGFW_writeFile(path, new Uint8Array(data), file.size); + } + }; + + reader.readAsArrayBuffer(file); + /* This works weird on modern OpenGL */ + var filename = stringToNewUTF8(path); + + filenamesArray.push(filename); + + Module._RGFW_makeSetValue(i, filename); + } + + Module._Emscripten_onDrop(count); + + for (var i = 0; i < count; i++) { + _free(filenamesArray[i]); + } + }, true); + + canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); + }); + + return win; +} + +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + return (u8)rgfw_keycode; /* TODO */ +} + +void RGFW_pollEvents(void) { + RGFW_resetPrevState(); + emscripten_sleep(0); +} + +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { + RGFW_UNUSED(win); + emscripten_set_canvas_element_size("#canvas", w, h); +} + +/* NOTE: I don't know if this is possible */ +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); } +/* this one might be possible but it looks iffy */ +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); return NULL; } + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_UNUSED(win); RGFW_UNUSED(mouse); } +void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_UNUSED(mouse); } + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + RGFW_UNUSED(win); + char* cursorName = NULL; + + switch (mouse) { + case RGFW_mouseNormal: cursorName = (char*)"default"; break; + case RGFW_mouseArrow: cursorName = (char*)"default"; break; + case RGFW_mouseIbeam: cursorName = (char*)"text"; break; + case RGFW_mouseCrosshair: cursorName = (char*)"crosshair"; break; + case RGFW_mousePointingHand: cursorName = (char*)"pointer"; break; + case RGFW_mouseResizeEW: cursorName = (char*)"ew-resize"; break; + case RGFW_mouseResizeNS: cursorName = (char*)"ns-resize"; break; + case RGFW_mouseResizeNWSE: cursorName = (char*)"nwse-resize"; break; + case RGFW_mouseResizeNESW: cursorName = (char*)"nesw-resize"; break; + case RGFW_mouseResizeNW: cursorName = (char*)"nw-resize"; break; + case RGFW_mouseResizeN: cursorName = (char*)"n-resize"; break; + case RGFW_mouseResizeNE: cursorName = (char*)"ne-resize"; break; + case RGFW_mouseResizeE: cursorName = (char*)"e-resize"; break; + case RGFW_mouseResizeSE: cursorName = (char*)"se-resize"; break; + case RGFW_mouseResizeS: cursorName = (char*)"s-resize"; break; + case RGFW_mouseResizeSW: cursorName = (char*)"sw-resize"; break; + case RGFW_mouseResizeW: cursorName = (char*)"w-resize"; break; + case RGFW_mouseResizeAll: cursorName = (char*)"move"; break; + case RGFW_mouseNotAllowed: cursorName = (char*)"not-allowed"; break; + case RGFW_mouseWait: cursorName = (char*)"wait"; break; + case RGFW_mouseProgress: cursorName = (char*)"progress"; break; + default: return RGFW_FALSE; + } + + EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, cursorName); + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseNormal); +} + +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, show); + if (show) + RGFW_window_setMouseDefault(win); + else + EM_ASM(document.getElementById('canvas').style.cursor = 'none';); +} + +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { + if(x) *x = EM_ASM_INT({ + return window.mouseX || 0; + }); + if (y) *y = EM_ASM_INT({ + return window.mouseY || 0; + }); + return RGFW_TRUE; +} + +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { + RGFW_UNUSED(win); + + EM_ASM_({ + var canvas = document.getElementById('canvas'); + if ($0) { + canvas.style.pointerEvents = 'none'; + } else { + canvas.style.pointerEvents = 'auto'; + } + }, passthrough); +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_UNUSED(textLen); + EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text); +} + + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); + /* + placeholder code for later + I'm not sure if this is possible do the the async stuff + */ + return 0; +} + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + win->src.ctx.native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + + EmscriptenWebGLContextAttributes attrs; + attrs.alpha = hints->alpha; + attrs.depth = hints->depth; + attrs.stencil = hints->stencil; + attrs.antialias = hints->samples; + attrs.premultipliedAlpha = EM_TRUE; + attrs.preserveDrawingBuffer = EM_FALSE; + + if (hints->doubleBuffer == 0) + attrs.renderViaOffscreenBackBuffer = 0; + else + attrs.renderViaOffscreenBackBuffer = hints->auxBuffers; + + attrs.failIfMajorPerformanceCaveat = EM_FALSE; + attrs.majorVersion = (hints->major == 0) ? 1 : hints->major; + attrs.minorVersion = hints->minor; + + attrs.enableExtensionsByDefault = EM_TRUE; + attrs.explicitSwapControl = EM_TRUE; + + emscripten_webgl_init_context_attributes(&attrs); + win->src.ctx.native->ctx = emscripten_webgl_create_context("#canvas", &attrs); + emscripten_webgl_make_context_current(win->src.ctx.native->ctx); + + #ifdef LEGACY_GL_EMULATION + EM_ASM("Module.useWebGL = true; GLImmediate.init();"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + #endif + + RGFW_window_swapInterval_OpenGL(win, 0); + + return RGFW_TRUE; +} + +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + emscripten_webgl_destroy_context(ctx->ctx); + win->src.ctx.native->ctx = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win) RGFW_ASSERT(win->src.ctx.native); + if (win == NULL) + emscripten_webgl_make_context_current(0); + else + emscripten_webgl_make_context_current(win->src.ctx.native->ctx); +} + +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + RGFW_ASSERT(win && win->src.ctx.native); + emscripten_webgl_commit_frame(); +} +void* RGFW_getCurrentContext_OpenGL(void) { return (void*)emscripten_webgl_get_current_context(); } + +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len) { + return EM_ASM_INT({ + var ext = UTF8ToString($0, $1); + var canvas = document.querySelector('canvas'); + var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + if (!gl) return 0; + + var supported = gl.getSupportedExtensions(); + return supported && supported.includes(ext) ? 1 : 0; + }, extension, len); + return RGFW_FALSE; +} + +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { + return (RGFW_proc)emscripten_webgl_get_proc_address(procname); + return NULL; +} + +#endif + +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } + +void RGFW_deinitPlatform(void) { } + +void RGFW_window_closePlatform(RGFW_window* win) { } + +int RGFW_innerWidth(void) { return EM_ASM_INT({ return window.innerWidth; }); } +int RGFW_innerHeight(void) { return EM_ASM_INT({ return window.innerHeight; }); } + +void RGFW_window_setRawMouseModePlatform(RGFW_window* win, RGFW_bool state) { + RGFW_UNUSED(win); RGFW_UNUSED(state); +} + +void RGFW_window_captureMousePlatform(RGFW_window* win, RGFW_bool state) { + RGFW_UNUSED(win); + if (state) { + emscripten_request_pointerlock("#canvas", 1); + } else { + emscripten_exit_pointerlock(); + } +} + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_UNUSED(win); + if (name == NULL) name = "\0"; + + emscripten_set_window_title(name); +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + RGFW_monitor* mon = RGFW_window_getMonitor(win); + if (mon != NULL) { + RGFW_window_resize(win, mon->mode.w, mon->mode.h); + } + + RGFW_window_move(win, 0, 0); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen) { + win->internal.flags |= RGFW_windowFullscreen; + EM_ASM( Module.requestFullscreen(false, true); ); + return; + } + win->internal.flags &= ~(u32)RGFW_windowFullscreen; + EM_ASM( Module.exitFullscreen(false, true); ); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + RGFW_UNUSED(win); + EM_ASM({ + var element = document.getElementById("canvas"); + if (element) + element.style.opacity = $1; + }, "elementId", opacity); +} + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUEmscriptenSurfaceSourceCanvasHTMLSelector canvasDesc = {0}; + canvasDesc.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; + canvasDesc.selector = (WGPUStringView){.data = "#canvas", .length = 7}; + + surfaceDesc.nextInChain = &canvasDesc.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + +u32 RGFW_WASMPhysicalToRGFW(u32 hash) { + switch(hash) { /* 0x0000 */ + case 0x67243A2DU /* Escape */: return RGFW_escape; /* 0x0001 */ + case 0x67251058U /* Digit0 */: return RGFW_0; /* 0x0002 */ + case 0x67251059U /* Digit1 */: return RGFW_1; /* 0x0003 */ + case 0x6725105AU /* Digit2 */: return RGFW_2; /* 0x0004 */ + case 0x6725105BU /* Digit3 */: return RGFW_3; /* 0x0005 */ + case 0x6725105CU /* Digit4 */: return RGFW_4; /* 0x0006 */ + case 0x6725105DU /* Digit5 */: return RGFW_5; /* 0x0007 */ + case 0x6725105EU /* Digit6 */: return RGFW_6; /* 0x0008 */ + case 0x6725105FU /* Digit7 */: return RGFW_7; /* 0x0009 */ + case 0x67251050U /* Digit8 */: return RGFW_8; /* 0x000A */ + case 0x67251051U /* Digit9 */: return RGFW_9; /* 0x000B */ + case 0x92E14DD3U /* Minus */: return RGFW_minus; /* 0x000C */ + case 0x92E1FBACU /* Equal */: return RGFW_equals; /* 0x000D */ + case 0x36BF1CB5U /* Backspace */: return RGFW_backSpace; /* 0x000E */ + case 0x7B8E51E2U /* Tab */: return RGFW_tab; /* 0x000F */ + case 0x2C595B51U /* KeyQ */: return RGFW_q; /* 0x0010 */ + case 0x2C595B57U /* KeyW */: return RGFW_w; /* 0x0011 */ + case 0x2C595B45U /* KeyE */: return RGFW_e; /* 0x0012 */ + case 0x2C595B52U /* KeyR */: return RGFW_r; /* 0x0013 */ + case 0x2C595B54U /* KeyT */: return RGFW_t; /* 0x0014 */ + case 0x2C595B59U /* KeyY */: return RGFW_y; /* 0x0015 */ + case 0x2C595B55U /* KeyU */: return RGFW_u; /* 0x0016 */ + case 0x2C595B4FU /* KeyO */: return RGFW_o; /* 0x0018 */ + case 0x2C595B50U /* KeyP */: return RGFW_p; /* 0x0019 */ + case 0x45D8158CU /* BracketLeft */: return RGFW_closeBracket; /* 0x001A */ + case 0xDEEABF7CU /* BracketRight */: return RGFW_bracket; /* 0x001B */ + case 0x92E1C5D2U /* Enter */: return RGFW_return; /* 0x001C */ + case 0xE058958CU /* ControlLeft */: return RGFW_controlL; /* 0x001D */ + case 0x2C595B41U /* KeyA */: return RGFW_a; /* 0x001E */ + case 0x2C595B53U /* KeyS */: return RGFW_s; /* 0x001F */ + case 0x2C595B44U /* KeyD */: return RGFW_d; /* 0x0020 */ + case 0x2C595B46U /* KeyF */: return RGFW_f; /* 0x0021 */ + case 0x2C595B47U /* KeyG */: return RGFW_g; /* 0x0022 */ + case 0x2C595B48U /* KeyH */: return RGFW_h; /* 0x0023 */ + case 0x2C595B4AU /* KeyJ */: return RGFW_j; /* 0x0024 */ + case 0x2C595B4BU /* KeyK */: return RGFW_k; /* 0x0025 */ + case 0x2C595B4CU /* KeyL */: return RGFW_l; /* 0x0026 */ + case 0x2707219EU /* Semicolon */: return RGFW_semicolon; /* 0x0027 */ + case 0x92E0B58DU /* Quote */: return RGFW_apostrophe; /* 0x0028 */ + case 0x36BF358DU /* Backquote */: return RGFW_backtick; /* 0x0029 */ + case 0x26B1958CU /* ShiftLeft */: return RGFW_shiftL; /* 0x002A */ + case 0x36BF2438U /* Backslash */: return RGFW_backSlash; /* 0x002B */ + case 0x2C595B5AU /* KeyZ */: return RGFW_z; /* 0x002C */ + case 0x2C595B58U /* KeyX */: return RGFW_x; /* 0x002D */ + case 0x2C595B43U /* KeyC */: return RGFW_c; /* 0x002E */ + case 0x2C595B56U /* KeyV */: return RGFW_v; /* 0x002F */ + case 0x2C595B42U /* KeyB */: return RGFW_b; /* 0x0030 */ + case 0x2C595B4EU /* KeyN */: return RGFW_n; /* 0x0031 */ + case 0x2C595B4DU /* KeyM */: return RGFW_m; /* 0x0032 */ + case 0x92E1A1C1U /* Comma */: return RGFW_comma; /* 0x0033 */ + case 0x672FFAD4U /* Period */: return RGFW_period; /* 0x0034 */ + case 0x92E0A438U /* Slash */: return RGFW_slash; /* 0x0035 */ + case 0xC5A6BF7CU /* ShiftRight */: return RGFW_shiftR; + case 0x5D64DA91U /* NumpadMultiply */: return RGFW_kpMultiply; + case 0xC914958CU /* AltLeft */: return RGFW_altL; /* 0x0038 */ + case 0x92E09CB5U /* Space */: return RGFW_space; /* 0x0039 */ + case 0xB8FAE73BU /* CapsLock */: return RGFW_capsLock; /* 0x003A */ + case 0x7174B789U /* F1 */: return RGFW_F1; /* 0x003B */ + case 0x7174B78AU /* F2 */: return RGFW_F2; /* 0x003C */ + case 0x7174B78BU /* F3 */: return RGFW_F3; /* 0x003D */ + case 0x7174B78CU /* F4 */: return RGFW_F4; /* 0x003E */ + case 0x7174B78DU /* F5 */: return RGFW_F5; /* 0x003F */ + case 0x7174B78EU /* F6 */: return RGFW_F6; /* 0x0040 */ + case 0x7174B78FU /* F7 */: return RGFW_F7; /* 0x0041 */ + case 0x7174B780U /* F8 */: return RGFW_F8; /* 0x0042 */ + case 0x7174B781U /* F9 */: return RGFW_F9; /* 0x0043 */ + case 0x7B8E57B0U /* F10 */: return RGFW_F10; /* 0x0044 */ + case 0xC925FCDFU /* Numpad7 */: return RGFW_kpMultiply; /* 0x0047 */ + case 0xC925FCD0U /* Numpad8 */: return RGFW_kp8; /* 0x0048 */ + case 0xC925FCD1U /* Numpad9 */: return RGFW_kp9; /* 0x0049 */ + case 0x5EA3E8A4U /* NumpadSubtract */: return RGFW_minus; /* 0x004A */ + case 0xC925FCDCU /* Numpad4 */: return RGFW_kp4; /* 0x004B */ + case 0xC925FCDDU /* Numpad5 */: return RGFW_kp5; /* 0x004C */ + case 0xC925FCDEU /* Numpad6 */: return RGFW_kp6; /* 0x004D */ + case 0xC925FCD9U /* Numpad1 */: return RGFW_kp1; /* 0x004F */ + case 0xC925FCDAU /* Numpad2 */: return RGFW_kp2; /* 0x0050 */ + case 0xC925FCDBU /* Numpad3 */: return RGFW_kp3; /* 0x0051 */ + case 0xC925FCD8U /* Numpad0 */: return RGFW_kp0; /* 0x0052 */ + case 0x95852DACU /* NumpadDecimal */: return RGFW_period; /* 0x0053 */ + case 0x7B8E57B1U /* F11 */: return RGFW_F11; /* 0x0057 */ + case 0x7B8E57B2U /* F12 */: return RGFW_F12; /* 0x0058 */ + case 0x7B8E57B3U /* F13 */: return DOM_PK_F13; /* 0x0064 */ + case 0x7B8E57B4U /* F14 */: return DOM_PK_F14; /* 0x0065 */ + case 0x7B8E57B5U /* F15 */: return DOM_PK_F15; /* 0x0066 */ + case 0x7B8E57B6U /* F16 */: return DOM_PK_F16; /* 0x0067 */ + case 0x7B8E57B7U /* F17 */: return DOM_PK_F17; /* 0x0068 */ + case 0x7B8E57B8U /* F18 */: return DOM_PK_F18; /* 0x0069 */ + case 0x7B8E57B9U /* F19 */: return DOM_PK_F19; /* 0x006A */ + case 0x7B8E57A8U /* F20 */: return DOM_PK_F20; /* 0x006B */ + case 0x7B8E57A9U /* F21 */: return DOM_PK_F21; /* 0x006C */ + case 0x7B8E57AAU /* F22 */: return DOM_PK_F22; /* 0x006D */ + case 0x7B8E57ABU /* F23 */: return DOM_PK_F23; /* 0x006E */ + case 0x7393FBACU /* NumpadEqual */: return RGFW_kpReturn; + case 0xB88EBF7CU /* AltRight */: return RGFW_altR; /* 0xE038 */ + case 0xC925873BU /* NumLock */: return RGFW_numLock; /* 0xE045 */ + case 0x2C595F45U /* Home */: return RGFW_home; /* 0xE047 */ + case 0xC91BB690U /* ArrowUp */: return RGFW_up; /* 0xE048 */ + case 0x672F9210U /* PageUp */: return RGFW_pageUp; /* 0xE049 */ + case 0x3799258CU /* ArrowLeft */: return RGFW_left; /* 0xE04B */ + case 0x4CE33F7CU /* ArrowRight */: return RGFW_right; /* 0xE04D */ + case 0x7B8E55DCU /* End */: return RGFW_end; /* 0xE04F */ + case 0x3799379EU /* ArrowDown */: return RGFW_down; /* 0xE050 */ + case 0xBA90179EU /* PageDown */: return RGFW_pageDown; /* 0xE051 */ + case 0x6723CB2CU /* Insert */: return RGFW_insert; /* 0xE052 */ + case 0x6725C50DU /* Delete */: return RGFW_delete; /* 0xE053 */ + case 0x6723658CU /* OSLeft */: return RGFW_superL; /* 0xE05B */ + case 0x39643F7CU /* MetaRight */: return RGFW_superR; /* 0xE05C */ + case 0x380B9C8CU /* NumpadAdd */: return DOM_PK_NUMPAD_ADD; /* 0x004E */ + default: return DOM_PK_UNKNOWN; + } + + return 0; +} + +/* unsupported functions */ +void RGFW_window_focus(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_raise(RGFW_window* win) { RGFW_UNUSED(win); } +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request) { RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); return RGFW_FALSE; } +RGFW_bool RGFW_monitor_getWorkarea(RGFW_monitor* monitor, i32* x, i32* y, i32* width, i32* height) { RGFW_UNUSED(monitor); RGFW_UNUSED(x); RGFW_UNUSED(width); RGFW_UNUSED(height); return RGFW_FALSE; } +size_t RGFW_monitor_getGammaRampPtr(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { RGFW_UNUSED(monitor); RGFW_UNUSED(ramp); return 0; } +RGFW_bool RGFW_monitor_setGammaRamp(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { RGFW_UNUSED(monitor); RGFW_UNUSED(ramp); return RGFW_FALSE; } +size_t RGFW_monitor_getModesPtr(RGFW_monitor* mon, RGFW_monitorMode** modes) { RGFW_UNUSED(mon); RGFW_UNUSED(modes); return 0; } +RGFW_bool RGFW_monitor_setMode(RGFW_monitor* mon, RGFW_monitorMode* mode) { RGFW_UNUSED(mon); RGFW_UNUSED(mode); return RGFW_FALSE; } +void RGFW_pollMonitors(void) { } +void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); } +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } +void RGFW_window_minimize(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_restore(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_UNUSED(win); RGFW_UNUSED(floating); } +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_UNUSED(win); RGFW_UNUSED(border); } +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { RGFW_UNUSED(win); RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); RGFW_UNUSED(type); return RGFW_FALSE; } +void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win); } +void RGFW_window_flash(RGFW_window* win, RGFW_flashRequest request) { RGFW_UNUSED(win); RGFW_UNUSED(request); } +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } +RGFW_monitor* RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +void RGFW_waitForEvent(i32 waitMS) { RGFW_UNUSED(waitMS); } +#endif + +/* end of web asm defines */ + +/* + * RGFW function pointer backend, made to allow you to compile for Wayland but fallback to X11 +*/ +#ifdef RGFW_DYNAMIC +typedef RGFW_window* (*RGFW_createWindowPlatform_ptr)(const char* name, RGFW_windowFlags flags, RGFW_window* win); +typedef RGFW_bool (*RGFW_getMouse_ptr)(i32* x, i32* y); +typedef u8 (*RGFW_rgfwToKeyChar_ptr)(u32 key); +typedef void (*RGFW_pollEvents_ptr)(void); +typedef void (*RGFW_pollMonitors_ptr)(void); +typedef void (*RGFW_window_move_ptr)(RGFW_window* win, i32 x, i32 y); +typedef void (*RGFW_window_resize_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_setAspectRatio_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_setMinSize_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_setMaxSize_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_maximize_ptr)(RGFW_window* win); +typedef void (*RGFW_window_focus_ptr)(RGFW_window* win); +typedef void (*RGFW_window_raise_ptr)(RGFW_window* win); +typedef void (*RGFW_window_setFullscreen_ptr)(RGFW_window* win, RGFW_bool fullscreen); +typedef void (*RGFW_window_setFloating_ptr)(RGFW_window* win, RGFW_bool floating); +typedef void (*RGFW_window_setOpacity_ptr)(RGFW_window* win, u8 opacity); +typedef void (*RGFW_window_minimize_ptr)(RGFW_window* win); +typedef void (*RGFW_window_restore_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_isFloating_ptr)(RGFW_window* win); +typedef void (*RGFW_window_setName_ptr)(RGFW_window* win, const char* name); +typedef void (*RGFW_window_setMousePassthrough_ptr)(RGFW_window* win, RGFW_bool passthrough); +typedef RGFW_bool (*RGFW_window_setIconEx_ptr)(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, u8 type); +typedef RGFW_mouse* (*RGFW_loadMouse_ptr)(u8* data, i32 w, i32 h, RGFW_format format); +typedef void (*RGFW_window_setMouse_ptr)(RGFW_window* win, RGFW_mouse* mouse); +typedef void (*RGFW_window_moveMouse_ptr)(RGFW_window* win, i32 x, i32 y); +typedef RGFW_bool (*RGFW_window_setMouseDefault_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_setMouseStandard_ptr)(RGFW_window* win, u8 mouse); +typedef void (*RGFW_window_hide_ptr)(RGFW_window* win); +typedef void (*RGFW_window_show_ptr)(RGFW_window* win); +typedef void (*RGFW_window_flash_ptr)(RGFW_window* win, RGFW_flashRequest request); +typedef RGFW_ssize_t (*RGFW_readClipboardPtr_ptr)(char* str, size_t strCapacity); +typedef void (*RGFW_writeClipboard_ptr)(const char* text, u32 textLen); +typedef RGFW_bool (*RGFW_window_isHidden_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_isMinimized_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_isMaximized_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_monitor_requestMode_ptr)(RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request); +typedef RGFW_bool (*RGFW_monitor_getWorkarea_ptr)(RGFW_monitor* mon, i32* x, i32* y, i32* w, i32* h); +typedef size_t (*RGFW_monitor_getModesPtr_ptr)(RGFW_monitor* mon, RGFW_monitorMode** modes); +typedef size_t (*RGFW_monitor_getGammaRampPtr_ptr) (RGFW_monitor* monitor, RGFW_gammaRamp* ramp); +typedef RGFW_bool (*RGFW_monitor_setGammaRamp_ptr) (RGFW_monitor* monitor, RGFW_gammaRamp* ramp); +typedef RGFW_bool (*RGFW_monitor_setMode_ptr)(RGFW_monitor* mon, RGFW_monitorMode* mode); +typedef RGFW_monitor* (*RGFW_window_getMonitor_ptr)(RGFW_window* win); +typedef void (*RGFW_window_closePlatform_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_createSurfacePtr_ptr)(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); +typedef void (*RGFW_window_blitSurface_ptr)(RGFW_window* win, RGFW_surface* surface); +typedef void (*RGFW_surface_freePtr_ptr)(RGFW_surface* surface); +typedef void (*RGFW_freeMouse_ptr)(RGFW_mouse* mouse); +typedef void (*RGFW_window_setBorder_ptr)(RGFW_window* win, RGFW_bool border); +typedef void (*RGFW_window_captureMousePlatform_ptr)(RGFW_window* win, RGFW_bool state); +typedef void (*RGFW_window_setRawMouseModePlatform_ptr)(RGFW_window* win, RGFW_bool state); +#ifdef RGFW_OPENGL +typedef void (*RGFW_window_makeCurrentContext_OpenGL_ptr)(RGFW_window* win); +typedef void* (*RGFW_getCurrentContext_OpenGL_ptr)(void); +typedef void (*RGFW_window_swapBuffers_OpenGL_ptr)(RGFW_window* win); +typedef void (*RGFW_window_swapInterval_OpenGL_ptr)(RGFW_window* win, i32 swapInterval); +typedef RGFW_bool (*RGFW_extensionSupportedPlatform_OpenGL_ptr)(const char* extension, size_t len); +typedef RGFW_proc (*RGFW_getProcAddress_OpenGL_ptr)(const char* procname); +typedef RGFW_bool (*RGFW_window_createContextPtr_OpenGL_ptr)(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints); +typedef void (*RGFW_window_deleteContextPtr_OpenGL_ptr)(RGFW_window* win, RGFW_glContext* ctx); +#endif +#ifdef RGFW_WEBGPU +typedef WGPUSurface (*RGFW_window_createSurface_WebGPU_ptr)(RGFW_window* window, WGPUInstance instance); +#endif + +/* Structure to hold all function pointers */ +typedef struct RGFW_FunctionPointers { + RGFW_createSurfacePtr_ptr createSurfacePtr; + RGFW_window_blitSurface_ptr window_blitSurface; + RGFW_surface_freePtr_ptr surface_freePtr; + RGFW_freeMouse_ptr freeMouse; + RGFW_window_setBorder_ptr window_setBorder; + RGFW_window_captureMousePlatform_ptr window_captureMousePlatform; + RGFW_window_setRawMouseModePlatform_ptr window_setRawMouseModePlatform; + RGFW_createWindowPlatform_ptr createWindowPlatform; + RGFW_getMouse_ptr getGlobalMouse; + RGFW_rgfwToKeyChar_ptr rgfwToKeyChar; + RGFW_pollEvents_ptr pollEvents; + RGFW_pollMonitors_ptr pollMonitors; + RGFW_window_move_ptr window_move; + RGFW_window_resize_ptr window_resize; + RGFW_window_setAspectRatio_ptr window_setAspectRatio; + RGFW_window_setMinSize_ptr window_setMinSize; + RGFW_window_setMaxSize_ptr window_setMaxSize; + RGFW_window_maximize_ptr window_maximize; + RGFW_window_focus_ptr window_focus; + RGFW_window_raise_ptr window_raise; + RGFW_window_setFullscreen_ptr window_setFullscreen; + RGFW_window_setFloating_ptr window_setFloating; + RGFW_window_setOpacity_ptr window_setOpacity; + RGFW_window_minimize_ptr window_minimize; + RGFW_window_restore_ptr window_restore; + RGFW_window_isFloating_ptr window_isFloating; + RGFW_window_setName_ptr window_setName; + RGFW_window_setMousePassthrough_ptr window_setMousePassthrough; + RGFW_window_setIconEx_ptr window_setIconEx; + RGFW_loadMouse_ptr loadMouse; + RGFW_window_setMouse_ptr window_setMouse; + RGFW_window_moveMouse_ptr window_moveMouse; + RGFW_window_setMouseDefault_ptr window_setMouseDefault; + RGFW_window_setMouseStandard_ptr window_setMouseStandard; + RGFW_window_hide_ptr window_hide; + RGFW_window_show_ptr window_show; + RGFW_window_flash_ptr window_flash; + RGFW_readClipboardPtr_ptr readClipboardPtr; + RGFW_writeClipboard_ptr writeClipboard; + RGFW_window_isHidden_ptr window_isHidden; + RGFW_window_isMinimized_ptr window_isMinimized; + RGFW_window_isMaximized_ptr window_isMaximized; + RGFW_monitor_requestMode_ptr monitor_requestMode; + RGFW_monitor_getWorkarea_ptr monitor_getWorkarea; + RGFW_monitor_getModesPtr_ptr monitor_getModesPtr; + RGFW_monitor_getGammaRampPtr_ptr monitor_getGammaRampPtr; + RGFW_monitor_setGammaRamp_ptr monitor_setGammaRamp; + RGFW_monitor_setMode_ptr monitor_setMode; + RGFW_window_getMonitor_ptr window_getMonitor; + RGFW_window_closePlatform_ptr window_closePlatform; +#ifdef RGFW_OPENGL + RGFW_extensionSupportedPlatform_OpenGL_ptr extensionSupportedPlatform_OpenGL; + RGFW_getProcAddress_OpenGL_ptr getProcAddress_OpenGL; + RGFW_window_createContextPtr_OpenGL_ptr window_createContextPtr_OpenGL; + RGFW_window_deleteContextPtr_OpenGL_ptr window_deleteContextPtr_OpenGL; + RGFW_window_makeCurrentContext_OpenGL_ptr window_makeCurrentContext_OpenGL; + RGFW_getCurrentContext_OpenGL_ptr getCurrentContext_OpenGL; + RGFW_window_swapBuffers_OpenGL_ptr window_swapBuffers_OpenGL; + RGFW_window_swapInterval_OpenGL_ptr window_swapInterval_OpenGL; +#endif +#ifdef RGFW_WEBGPU + RGFW_window_createSurface_WebGPU_ptr window_createSurface_WebGPU; +#endif +} RGFW_functionPointers; + +RGFW_functionPointers RGFW_api; + +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { return RGFW_api.createSurfacePtr(data, w, h, format, surface); } +void RGFW_surface_freePtr(RGFW_surface* surface) { RGFW_api.surface_freePtr(surface); } +void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_api.freeMouse(mouse); } +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { RGFW_api.window_blitSurface(win, surface); } +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_api.window_setBorder(win, border); } +void RGFW_window_captureMousePlatform(RGFW_window* win, RGFW_bool state) { RGFW_api.window_captureMousePlatform(win, state); } +void RGFW_window_setRawMouseModePlatform(RGFW_window* win, RGFW_bool state) { RGFW_api.window_setRawMouseModePlatform(win, state); } +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { RGFW_init(); return RGFW_api.createWindowPlatform(name, flags, win); } +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { return RGFW_api.getGlobalMouse(x, y); } +u8 RGFW_rgfwToKeyChar(u32 key) { return RGFW_api.rgfwToKeyChar(key); } +void RGFW_pollEvents(void) { RGFW_api.pollEvents(); } +void RGFW_pollMonitors(void) { RGFW_api.pollMonitors(); } +void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { RGFW_api.window_move(win, x, y); } +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_resize(win, w, h); } +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setAspectRatio(win, w, h); } +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setMinSize(win, w, h); } +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setMaxSize(win, w, h); } +void RGFW_window_maximize(RGFW_window* win) { RGFW_api.window_maximize(win); } +void RGFW_window_focus(RGFW_window* win) { RGFW_api.window_focus(win); } +void RGFW_window_raise(RGFW_window* win) { RGFW_api.window_raise(win); } +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { RGFW_api.window_setFullscreen(win, fullscreen); } +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_api.window_setFloating(win, floating); } +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { RGFW_api.window_setOpacity(win, opacity); } +void RGFW_window_minimize(RGFW_window* win) { RGFW_api.window_minimize(win); } +void RGFW_window_restore(RGFW_window* win) { RGFW_api.window_restore(win); } +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { return RGFW_api.window_isFloating(win); } +void RGFW_window_setName(RGFW_window* win, const char* name) { RGFW_api.window_setName(win, name); } + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { RGFW_api.window_setMousePassthrough(win, passthrough); } +#endif + +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, u8 type) { return RGFW_api.window_setIconEx(win, data, w, h, format, type); } +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { return RGFW_api.loadMouse(data, w, h, format); } +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_api.window_setMouse(win, mouse); } +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_api.window_moveMouse(win, x, y); } +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { return RGFW_api.window_setMouseDefault(win); } +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { return RGFW_api.window_setMouseStandard(win, mouse); } +void RGFW_window_hide(RGFW_window* win) { RGFW_api.window_hide(win); } +void RGFW_window_show(RGFW_window* win) { RGFW_api.window_show(win); } +void RGFW_window_flash(RGFW_window* win, RGFW_flashRequest request) { RGFW_api.window_flash(win, request); } +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { return RGFW_api.readClipboardPtr(str, strCapacity); } +void RGFW_writeClipboard(const char* text, u32 textLen) { RGFW_api.writeClipboard(text, textLen); } +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { return RGFW_api.window_isHidden(win); } +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { return RGFW_api.window_isMinimized(win); } +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { return RGFW_api.window_isMaximized(win); } +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor* mon, RGFW_monitorMode* mode, RGFW_modeRequest request) { return RGFW_api.monitor_requestMode(mon, mode, request); } +RGFW_bool RGFW_monitor_getWorkarea(RGFW_monitor* monitor, i32* x, i32* y, i32* width, i32* height) { return RGFW_api.monitor_getWorkarea(monitor, x, y, width, height); } +size_t RGFW_monitor_getGammaRampPtr(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { return RGFW_api.monitor_getGammaRampPtr(monitor, ramp); } +RGFW_bool RGFW_monitor_setGammaRamp(RGFW_monitor* monitor, RGFW_gammaRamp* ramp) { return RGFW_api.monitor_setGammaRamp(monitor, ramp); } +size_t RGFW_monitor_getModesPtr(RGFW_monitor* mon, RGFW_monitorMode** modes) { return RGFW_api.monitor_getModesPtr(mon, modes); } +RGFW_bool RGFW_monitor_setMode(RGFW_monitor* mon, RGFW_monitorMode* mode) { return RGFW_api.monitor_setMode(mon, mode); } +RGFW_monitor* RGFW_window_getMonitor(RGFW_window* win) { return RGFW_api.window_getMonitor(win); } +void RGFW_window_closePlatform(RGFW_window* win) { RGFW_api.window_closePlatform(win); } + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len) { return RGFW_api.extensionSupportedPlatform_OpenGL(extension, len); } +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { return RGFW_api.getProcAddress_OpenGL(procname); } +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { return RGFW_api.window_createContextPtr_OpenGL(win, ctx, hints); } +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { RGFW_api.window_deleteContextPtr_OpenGL(win, ctx); } +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { RGFW_api.window_makeCurrentContext_OpenGL(win); } +void* RGFW_getCurrentContext_OpenGL(void) { return RGFW_api.getCurrentContext_OpenGL(); } +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { RGFW_api.window_swapBuffers_OpenGL(win); } +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { RGFW_api.window_swapInterval_OpenGL(win, swapInterval); } +#endif + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { return RGFW_api.window_createSurface_WebGPU(window, instance); } +#endif +#endif /* RGFW_DYNAMIC */ + +/* + * start of X11 AND wayland defines + * this allows a single executable to support x11 AND wayland + * falling back to x11 if wayland fails to initalize +*/ +#if defined(RGFW_WAYLAND) && defined(RGFW_X11) +void RGFW_load_X11(void) { + RGFW_api.createSurfacePtr = RGFW_createSurfacePtr_X11; + RGFW_api.window_blitSurface = RGFW_window_blitSurface_X11; + RGFW_api.surface_freePtr = RGFW_surface_freePtr_X11; + RGFW_api.freeMouse = RGFW_freeMouse_X11; + RGFW_api.window_setBorder = RGFW_window_setBorder_X11; + RGFW_api.window_captureMousePlatform = RGFW_window_captureMousePlatform_X11; + RGFW_api.window_setRawMouseModePlatform = RGFW_window_setRawMouseModePlatform_X11; + RGFW_api.createWindowPlatform = RGFW_createWindowPlatform_X11; + RGFW_api.getGlobalMouse = RGFW_getGlobalMouse_X11; + RGFW_api.rgfwToKeyChar = RGFW_rgfwToKeyChar_X11; + RGFW_api.pollEvents = RGFW_pollEvents_X11; + RGFW_api.pollMonitors = RGFW_pollMonitors_X11; + RGFW_api.window_move = RGFW_window_move_X11; + RGFW_api.window_resize = RGFW_window_resize_X11; + RGFW_api.window_setAspectRatio = RGFW_window_setAspectRatio_X11; + RGFW_api.window_setMinSize = RGFW_window_setMinSize_X11; + RGFW_api.window_setMaxSize = RGFW_window_setMaxSize_X11; + RGFW_api.window_maximize = RGFW_window_maximize_X11; + RGFW_api.window_focus = RGFW_window_focus_X11; + RGFW_api.window_raise = RGFW_window_raise_X11; + RGFW_api.window_setFullscreen = RGFW_window_setFullscreen_X11; + RGFW_api.window_setFloating = RGFW_window_setFloating_X11; + RGFW_api.window_setOpacity = RGFW_window_setOpacity_X11; + RGFW_api.window_minimize = RGFW_window_minimize_X11; + RGFW_api.window_restore = RGFW_window_restore_X11; + RGFW_api.window_isFloating = RGFW_window_isFloating_X11; + RGFW_api.window_setName = RGFW_window_setName_X11; +#ifndef RGFW_NO_PASSTHROUGH + RGFW_api.window_setMousePassthrough = RGFW_window_setMousePassthrough_X11; +#endif + RGFW_api.window_setIconEx = RGFW_window_setIconEx_X11; + RGFW_api.loadMouse = RGFW_loadMouse_X11; + RGFW_api.window_setMouse = RGFW_window_setMouse_X11; + RGFW_api.window_moveMouse = RGFW_window_moveMouse_X11; + RGFW_api.window_setMouseDefault = RGFW_window_setMouseDefault_X11; + RGFW_api.window_setMouseStandard = RGFW_window_setMouseStandard_X11; + RGFW_api.window_hide = RGFW_window_hide_X11; + RGFW_api.window_show = RGFW_window_show_X11; + RGFW_api.window_flash = RGFW_window_flash_X11; + RGFW_api.readClipboardPtr = RGFW_readClipboardPtr_X11; + RGFW_api.writeClipboard = RGFW_writeClipboard_X11; + RGFW_api.window_isHidden = RGFW_window_isHidden_X11; + RGFW_api.window_isMinimized = RGFW_window_isMinimized_X11; + RGFW_api.window_isMaximized = RGFW_window_isMaximized_X11; + RGFW_api.monitor_requestMode = RGFW_monitor_requestMode_X11; + RGFW_api.monitor_getModesPtr = RGFW_monitor_getModesPtr_X11; + RGFW_api.monitor_setGammaRamp = RGFW_monitor_setGammaRamp_X11; + RGFW_api.monitor_getGammaRampPtr = RGFW_monitor_getGammaRampPtr_X11; + RGFW_api.monitor_setMode = RGFW_monitor_setMode_X11; + RGFW_api.window_getMonitor = RGFW_window_getMonitor_X11; + RGFW_api.window_closePlatform = RGFW_window_closePlatform_X11; +#ifdef RGFW_OPENGL + RGFW_api.extensionSupportedPlatform_OpenGL = RGFW_extensionSupportedPlatform_OpenGL_X11; + RGFW_api.getProcAddress_OpenGL = RGFW_getProcAddress_OpenGL_X11; + RGFW_api.window_createContextPtr_OpenGL = RGFW_window_createContextPtr_OpenGL_X11; + RGFW_api.window_deleteContextPtr_OpenGL = RGFW_window_deleteContextPtr_OpenGL_X11; + RGFW_api.window_makeCurrentContext_OpenGL = RGFW_window_makeCurrentContext_OpenGL_X11; + RGFW_api.getCurrentContext_OpenGL = RGFW_getCurrentContext_OpenGL_X11; + RGFW_api.window_swapBuffers_OpenGL = RGFW_window_swapBuffers_OpenGL_X11; + RGFW_api.window_swapInterval_OpenGL = RGFW_window_swapInterval_OpenGL_X11; +#endif +#ifdef RGFW_WEBGPU + RGFW_api.window_createSurface_WebGPU = RGFW_window_createSurface_WebGPU_X11; +#endif +} + +void RGFW_load_Wayland(void) { + RGFW_api.createSurfacePtr = RGFW_createSurfacePtr_Wayland; + RGFW_api.window_blitSurface = RGFW_window_blitSurface_Wayland; + RGFW_api.surface_freePtr = RGFW_surface_freePtr_Wayland; + RGFW_api.freeMouse = RGFW_freeMouse_Wayland; + RGFW_api.window_setBorder = RGFW_window_setBorder_Wayland; + RGFW_api.window_captureMousePlatform = RGFW_window_captureMousePlatform_Wayland; + RGFW_api.window_setRawMouseModePlatform = RGFW_window_setRawMouseModePlatform_Wayland; + RGFW_api.createWindowPlatform = RGFW_createWindowPlatform_Wayland; + RGFW_api.getGlobalMouse = RGFW_getGlobalMouse_Wayland; + RGFW_api.rgfwToKeyChar = RGFW_rgfwToKeyChar_Wayland; + RGFW_api.pollEvents = RGFW_pollEvents_Wayland; + RGFW_api.pollMonitors = RGFW_pollMonitors_Wayland; + RGFW_api.window_move = RGFW_window_move_Wayland; + RGFW_api.window_resize = RGFW_window_resize_Wayland; + RGFW_api.window_setAspectRatio = RGFW_window_setAspectRatio_Wayland; + RGFW_api.window_setMinSize = RGFW_window_setMinSize_Wayland; + RGFW_api.window_setMaxSize = RGFW_window_setMaxSize_Wayland; + RGFW_api.window_maximize = RGFW_window_maximize_Wayland; + RGFW_api.window_focus = RGFW_window_focus_Wayland; + RGFW_api.window_raise = RGFW_window_raise_Wayland; + RGFW_api.window_setFullscreen = RGFW_window_setFullscreen_Wayland; + RGFW_api.window_setFloating = RGFW_window_setFloating_Wayland; + RGFW_api.window_setOpacity = RGFW_window_setOpacity_Wayland; + RGFW_api.window_minimize = RGFW_window_minimize_Wayland; + RGFW_api.window_restore = RGFW_window_restore_Wayland; + RGFW_api.window_isFloating = RGFW_window_isFloating_Wayland; + RGFW_api.window_setName = RGFW_window_setName_Wayland; +#ifndef RGFW_NO_PASSTHROUGH + RGFW_api.window_setMousePassthrough = RGFW_window_setMousePassthrough_Wayland; +#endif + RGFW_api.window_setIconEx = RGFW_window_setIconEx_Wayland; + RGFW_api.loadMouse = RGFW_loadMouse_Wayland; + RGFW_api.window_setMouse = RGFW_window_setMouse_Wayland; + RGFW_api.window_moveMouse = RGFW_window_moveMouse_Wayland; + RGFW_api.window_setMouseDefault = RGFW_window_setMouseDefault_Wayland; + RGFW_api.window_setMouseStandard = RGFW_window_setMouseStandard_Wayland; + RGFW_api.window_hide = RGFW_window_hide_Wayland; + RGFW_api.window_show = RGFW_window_show_Wayland; + RGFW_api.window_flash = RGFW_window_flash_X11; + RGFW_api.readClipboardPtr = RGFW_readClipboardPtr_Wayland; + RGFW_api.writeClipboard = RGFW_writeClipboard_Wayland; + RGFW_api.window_isHidden = RGFW_window_isHidden_Wayland; + RGFW_api.window_isMinimized = RGFW_window_isMinimized_Wayland; + RGFW_api.window_isMaximized = RGFW_window_isMaximized_Wayland; + RGFW_api.monitor_requestMode = RGFW_monitor_requestMode_Wayland; + RGFW_api.monitor_getModesPtr = RGFW_monitor_getModesPtr_Wayland; + RGFW_api.monitor_setGammaRamp = RGFW_monitor_setGammaRamp_Wayland; + RGFW_api.monitor_getGammaRampPtr = RGFW_monitor_getGammaRampPtr_Wayland; + RGFW_api.monitor_setMode = RGFW_monitor_setMode_Wayland; + RGFW_api.window_getMonitor = RGFW_window_getMonitor_Wayland; + RGFW_api.window_closePlatform = RGFW_window_closePlatform_Wayland; +#ifdef RGFW_OPENGL + RGFW_api.extensionSupportedPlatform_OpenGL = RGFW_extensionSupportedPlatform_OpenGL_Wayland; + RGFW_api.getProcAddress_OpenGL = RGFW_getProcAddress_OpenGL_Wayland; + RGFW_api.window_createContextPtr_OpenGL = RGFW_window_createContextPtr_OpenGL_Wayland; + RGFW_api.window_deleteContextPtr_OpenGL = RGFW_window_deleteContextPtr_OpenGL_Wayland; + RGFW_api.window_makeCurrentContext_OpenGL = RGFW_window_makeCurrentContext_OpenGL_Wayland; + RGFW_api.getCurrentContext_OpenGL = RGFW_getCurrentContext_OpenGL_Wayland; + RGFW_api.window_swapBuffers_OpenGL = RGFW_window_swapBuffers_OpenGL_Wayland; + RGFW_api.window_swapInterval_OpenGL = RGFW_window_swapInterval_OpenGL_Wayland; +#endif +#ifdef RGFW_WEBGPU + RGFW_api.window_createSurface_WebGPU = RGFW_window_createSurface_WebGPU_Wayland; +#endif +} +#endif /* wayland AND x11 */ +/* end of X11 AND wayland defines */ + +#endif /* RGFW_IMPLEMENTATION */ + +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) +} +#endif + +#if _MSC_VER + #pragma warning( pop ) +#endif + diff --git a/shf/ffi/glad.c b/shf/ffi/glad.c new file mode 100644 index 0000000..a9c0cb0 --- /dev/null +++ b/shf/ffi/glad.c @@ -0,0 +1,1463 @@ +/* + + OpenGL loader generated by glad 0.1.27 on Tue Sep 4 23:27:29 2018. + + Language/Generator: C/C++ + Specification: gl + APIs: gl=2.1 + Profile: compatibility + Extensions: + + Loader: True + Local files: False + Omit khrplatform: False + + Commandline: + --profile="compatibility" --api="gl=2.1" --generator="c" --spec="gl" --extensions="" + Online: + http://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D2.1 +*/ + +#include +#include +#include +#include "glad.h" + +static void* get_proc(const char *namez); + +#if defined(_WIN32) || defined(__CYGWIN__) +#include +static HMODULE libGL; + +typedef void* (APIENTRYP PFNWGLGETPROCADDRESSPROC_PRIVATE)(const char*); +static PFNWGLGETPROCADDRESSPROC_PRIVATE gladGetProcAddressPtr; + +#ifdef _MSC_VER +#ifdef __has_include + #if __has_include() + #define HAVE_WINAPIFAMILY 1 + #endif +#elif _MSC_VER >= 1700 && !_USING_V110_SDK71_ + #define HAVE_WINAPIFAMILY 1 +#endif +#endif + +#ifdef HAVE_WINAPIFAMILY + #include + #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) + #define IS_UWP 1 + #endif +#endif + +static +int open_gl(void) { +#ifndef IS_UWP + libGL = LoadLibraryW(L"opengl32.dll"); + if(libGL != NULL) { + void (* tmp)(void); + tmp = (void(*)(void)) GetProcAddress(libGL, "wglGetProcAddress"); + gladGetProcAddressPtr = (PFNWGLGETPROCADDRESSPROC_PRIVATE) tmp; + return gladGetProcAddressPtr != NULL; + } +#endif + + return 0; +} + +static +void close_gl(void) { + if(libGL != NULL) { + FreeLibrary((HMODULE) libGL); + libGL = NULL; + } +} +#else +#include +static void* libGL; + +#if !defined(__APPLE__) && !defined(__HAIKU__) +typedef void* (APIENTRYP PFNGLXGETPROCADDRESSPROC_PRIVATE)(const char*); +static PFNGLXGETPROCADDRESSPROC_PRIVATE gladGetProcAddressPtr; +#endif + +static +int open_gl(void) { +#ifdef __APPLE__ + static const char *NAMES[] = { + "../Frameworks/OpenGL.framework/OpenGL", + "/Library/Frameworks/OpenGL.framework/OpenGL", + "/System/Library/Frameworks/OpenGL.framework/OpenGL", + "/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL" + }; +#else + static const char *NAMES[] = {"libGL.so.1", "libGL.so"}; +#endif + + unsigned int index = 0; + for(index = 0; index < (sizeof(NAMES) / sizeof(NAMES[0])); index++) { + libGL = dlopen(NAMES[index], RTLD_NOW | RTLD_GLOBAL); + + if(libGL != NULL) { +#if defined(__APPLE__) || defined(__HAIKU__) + return 1; +#else + gladGetProcAddressPtr = (PFNGLXGETPROCADDRESSPROC_PRIVATE)dlsym(libGL, + "glXGetProcAddressARB"); + return gladGetProcAddressPtr != NULL; +#endif + } + } + + return 0; +} + +static +void close_gl(void) { + if(libGL != NULL) { + dlclose(libGL); + libGL = NULL; + } +} +#endif + +static +void* get_proc(const char *namez) { + void* result = NULL; + if(libGL == NULL) return NULL; + +#if !defined(__APPLE__) && !defined(__HAIKU__) + if(gladGetProcAddressPtr != NULL) { + result = gladGetProcAddressPtr(namez); + } +#endif + if(result == NULL) { +#if defined(_WIN32) || defined(__CYGWIN__) + result = (void*)GetProcAddress((HMODULE) libGL, namez); +#else + result = dlsym(libGL, namez); +#endif + } + + return result; +} + +int gladLoadGL(void) { + int status = 0; + + if(open_gl()) { + status = gladLoadGLLoader(&get_proc); + close_gl(); + } + + return status; +} + +struct gladGLversionStruct GLVersion = { 0, 0 }; + +#if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_3_0) +#define _GLAD_IS_SOME_NEW_VERSION 1 +#endif + +static int max_loaded_major; +static int max_loaded_minor; + +static const char *exts = NULL; +static int num_exts_i = 0; +static char **exts_i = NULL; + +static int get_exts(void) { +#ifdef _GLAD_IS_SOME_NEW_VERSION + if(max_loaded_major < 3) { +#endif + exts = (const char *)glGetString(GL_EXTENSIONS); +#ifdef _GLAD_IS_SOME_NEW_VERSION + } else { + unsigned int index; + + num_exts_i = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts_i); + if (num_exts_i > 0) { + exts_i = (char **)realloc((void *)exts_i, (size_t)num_exts_i * (sizeof *exts_i)); + } + + if (exts_i == NULL) { + return 0; + } + + for(index = 0; index < (unsigned)num_exts_i; index++) { + const char *gl_str_tmp = (const char*)glGetStringi(GL_EXTENSIONS, index); + size_t len = strlen(gl_str_tmp); + + char *local_str = (char*)malloc((len+1) * sizeof(char)); + if(local_str != NULL) { + memcpy(local_str, gl_str_tmp, (len+1) * sizeof(char)); + } + exts_i[index] = local_str; + } + } +#endif + return 1; +} + +static void free_exts(void) { + if (exts_i != NULL) { + int index; + for(index = 0; index < num_exts_i; index++) { + free((char *)exts_i[index]); + } + free((void *)exts_i); + exts_i = NULL; + } +} + +static int has_ext(const char *ext) { +#ifdef _GLAD_IS_SOME_NEW_VERSION + if(max_loaded_major < 3) { +#endif + const char *extensions; + const char *loc; + const char *terminator; + extensions = exts; + if(extensions == NULL || ext == NULL) { + return 0; + } + + while(1) { + loc = strstr(extensions, ext); + if(loc == NULL) { + return 0; + } + + terminator = loc + strlen(ext); + if((loc == extensions || *(loc - 1) == ' ') && + (*terminator == ' ' || *terminator == '\0')) { + return 1; + } + extensions = terminator; + } +#ifdef _GLAD_IS_SOME_NEW_VERSION + } else { + int index; + if(exts_i == NULL) return 0; + for(index = 0; index < num_exts_i; index++) { + const char *e = exts_i[index]; + + if(exts_i[index] != NULL && strcmp(e, ext) == 0) { + return 1; + } + } + } +#endif + + return 0; +} +int GLAD_GL_VERSION_1_0 = 0; +int GLAD_GL_VERSION_1_1 = 0; +int GLAD_GL_VERSION_1_2 = 0; +int GLAD_GL_VERSION_1_3 = 0; +int GLAD_GL_VERSION_1_4 = 0; +int GLAD_GL_VERSION_1_5 = 0; +int GLAD_GL_VERSION_2_0 = 0; +int GLAD_GL_VERSION_2_1 = 0; +PFNGLACCUMPROC glad_glAccum = NULL; +PFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL; +PFNGLALPHAFUNCPROC glad_glAlphaFunc = NULL; +PFNGLARETEXTURESRESIDENTPROC glad_glAreTexturesResident = NULL; +PFNGLARRAYELEMENTPROC glad_glArrayElement = NULL; +PFNGLATTACHSHADERPROC glad_glAttachShader = NULL; +PFNGLBEGINPROC glad_glBegin = NULL; +PFNGLBEGINQUERYPROC glad_glBeginQuery = NULL; +PFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation = NULL; +PFNGLBINDBUFFERPROC glad_glBindBuffer = NULL; +PFNGLBINDTEXTUREPROC glad_glBindTexture = NULL; +PFNGLBITMAPPROC glad_glBitmap = NULL; +PFNGLBLENDCOLORPROC glad_glBlendColor = NULL; +PFNGLBLENDEQUATIONPROC glad_glBlendEquation = NULL; +PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate = NULL; +PFNGLBLENDFUNCPROC glad_glBlendFunc = NULL; +PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL; +PFNGLBUFFERDATAPROC glad_glBufferData = NULL; +PFNGLBUFFERSUBDATAPROC glad_glBufferSubData = NULL; +PFNGLCALLLISTPROC glad_glCallList = NULL; +PFNGLCALLLISTSPROC glad_glCallLists = NULL; +PFNGLCLEARPROC glad_glClear = NULL; +PFNGLCLEARACCUMPROC glad_glClearAccum = NULL; +PFNGLCLEARCOLORPROC glad_glClearColor = NULL; +PFNGLCLEARDEPTHPROC glad_glClearDepth = NULL; +PFNGLCLEARINDEXPROC glad_glClearIndex = NULL; +PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL; +PFNGLCLIENTACTIVETEXTUREPROC glad_glClientActiveTexture = NULL; +PFNGLCLIPPLANEPROC glad_glClipPlane = NULL; +PFNGLCOLOR3BPROC glad_glColor3b = NULL; +PFNGLCOLOR3BVPROC glad_glColor3bv = NULL; +PFNGLCOLOR3DPROC glad_glColor3d = NULL; +PFNGLCOLOR3DVPROC glad_glColor3dv = NULL; +PFNGLCOLOR3FPROC glad_glColor3f = NULL; +PFNGLCOLOR3FVPROC glad_glColor3fv = NULL; +PFNGLCOLOR3IPROC glad_glColor3i = NULL; +PFNGLCOLOR3IVPROC glad_glColor3iv = NULL; +PFNGLCOLOR3SPROC glad_glColor3s = NULL; +PFNGLCOLOR3SVPROC glad_glColor3sv = NULL; +PFNGLCOLOR3UBPROC glad_glColor3ub = NULL; +PFNGLCOLOR3UBVPROC glad_glColor3ubv = NULL; +PFNGLCOLOR3UIPROC glad_glColor3ui = NULL; +PFNGLCOLOR3UIVPROC glad_glColor3uiv = NULL; +PFNGLCOLOR3USPROC glad_glColor3us = NULL; +PFNGLCOLOR3USVPROC glad_glColor3usv = NULL; +PFNGLCOLOR4BPROC glad_glColor4b = NULL; +PFNGLCOLOR4BVPROC glad_glColor4bv = NULL; +PFNGLCOLOR4DPROC glad_glColor4d = NULL; +PFNGLCOLOR4DVPROC glad_glColor4dv = NULL; +PFNGLCOLOR4FPROC glad_glColor4f = NULL; +PFNGLCOLOR4FVPROC glad_glColor4fv = NULL; +PFNGLCOLOR4IPROC glad_glColor4i = NULL; +PFNGLCOLOR4IVPROC glad_glColor4iv = NULL; +PFNGLCOLOR4SPROC glad_glColor4s = NULL; +PFNGLCOLOR4SVPROC glad_glColor4sv = NULL; +PFNGLCOLOR4UBPROC glad_glColor4ub = NULL; +PFNGLCOLOR4UBVPROC glad_glColor4ubv = NULL; +PFNGLCOLOR4UIPROC glad_glColor4ui = NULL; +PFNGLCOLOR4UIVPROC glad_glColor4uiv = NULL; +PFNGLCOLOR4USPROC glad_glColor4us = NULL; +PFNGLCOLOR4USVPROC glad_glColor4usv = NULL; +PFNGLCOLORMASKPROC glad_glColorMask = NULL; +PFNGLCOLORMATERIALPROC glad_glColorMaterial = NULL; +PFNGLCOLORPOINTERPROC glad_glColorPointer = NULL; +PFNGLCOMPILESHADERPROC glad_glCompileShader = NULL; +PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D = NULL; +PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D = NULL; +PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D = NULL; +PFNGLCOPYPIXELSPROC glad_glCopyPixels = NULL; +PFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D = NULL; +PFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D = NULL; +PFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D = NULL; +PFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D = NULL; +PFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D = NULL; +PFNGLCREATEPROGRAMPROC glad_glCreateProgram = NULL; +PFNGLCREATESHADERPROC glad_glCreateShader = NULL; +PFNGLCULLFACEPROC glad_glCullFace = NULL; +PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers = NULL; +PFNGLDELETELISTSPROC glad_glDeleteLists = NULL; +PFNGLDELETEPROGRAMPROC glad_glDeleteProgram = NULL; +PFNGLDELETEQUERIESPROC glad_glDeleteQueries = NULL; +PFNGLDELETESHADERPROC glad_glDeleteShader = NULL; +PFNGLDELETETEXTURESPROC glad_glDeleteTextures = NULL; +PFNGLDEPTHFUNCPROC glad_glDepthFunc = NULL; +PFNGLDEPTHMASKPROC glad_glDepthMask = NULL; +PFNGLDEPTHRANGEPROC glad_glDepthRange = NULL; +PFNGLDETACHSHADERPROC glad_glDetachShader = NULL; +PFNGLDISABLEPROC glad_glDisable = NULL; +PFNGLDISABLECLIENTSTATEPROC glad_glDisableClientState = NULL; +PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray = NULL; +PFNGLDRAWARRAYSPROC glad_glDrawArrays = NULL; +PFNGLDRAWBUFFERPROC glad_glDrawBuffer = NULL; +PFNGLDRAWBUFFERSPROC glad_glDrawBuffers = NULL; +PFNGLDRAWELEMENTSPROC glad_glDrawElements = NULL; +PFNGLDRAWPIXELSPROC glad_glDrawPixels = NULL; +PFNGLDRAWRANGEELEMENTSPROC glad_glDrawRangeElements = NULL; +PFNGLEDGEFLAGPROC glad_glEdgeFlag = NULL; +PFNGLEDGEFLAGPOINTERPROC glad_glEdgeFlagPointer = NULL; +PFNGLEDGEFLAGVPROC glad_glEdgeFlagv = NULL; +PFNGLENABLEPROC glad_glEnable = NULL; +PFNGLENABLECLIENTSTATEPROC glad_glEnableClientState = NULL; +PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray = NULL; +PFNGLENDPROC glad_glEnd = NULL; +PFNGLENDLISTPROC glad_glEndList = NULL; +PFNGLENDQUERYPROC glad_glEndQuery = NULL; +PFNGLEVALCOORD1DPROC glad_glEvalCoord1d = NULL; +PFNGLEVALCOORD1DVPROC glad_glEvalCoord1dv = NULL; +PFNGLEVALCOORD1FPROC glad_glEvalCoord1f = NULL; +PFNGLEVALCOORD1FVPROC glad_glEvalCoord1fv = NULL; +PFNGLEVALCOORD2DPROC glad_glEvalCoord2d = NULL; +PFNGLEVALCOORD2DVPROC glad_glEvalCoord2dv = NULL; +PFNGLEVALCOORD2FPROC glad_glEvalCoord2f = NULL; +PFNGLEVALCOORD2FVPROC glad_glEvalCoord2fv = NULL; +PFNGLEVALMESH1PROC glad_glEvalMesh1 = NULL; +PFNGLEVALMESH2PROC glad_glEvalMesh2 = NULL; +PFNGLEVALPOINT1PROC glad_glEvalPoint1 = NULL; +PFNGLEVALPOINT2PROC glad_glEvalPoint2 = NULL; +PFNGLFEEDBACKBUFFERPROC glad_glFeedbackBuffer = NULL; +PFNGLFINISHPROC glad_glFinish = NULL; +PFNGLFLUSHPROC glad_glFlush = NULL; +PFNGLFOGCOORDPOINTERPROC glad_glFogCoordPointer = NULL; +PFNGLFOGCOORDDPROC glad_glFogCoordd = NULL; +PFNGLFOGCOORDDVPROC glad_glFogCoorddv = NULL; +PFNGLFOGCOORDFPROC glad_glFogCoordf = NULL; +PFNGLFOGCOORDFVPROC glad_glFogCoordfv = NULL; +PFNGLFOGFPROC glad_glFogf = NULL; +PFNGLFOGFVPROC glad_glFogfv = NULL; +PFNGLFOGIPROC glad_glFogi = NULL; +PFNGLFOGIVPROC glad_glFogiv = NULL; +PFNGLFRONTFACEPROC glad_glFrontFace = NULL; +PFNGLFRUSTUMPROC glad_glFrustum = NULL; +PFNGLGENBUFFERSPROC glad_glGenBuffers = NULL; +PFNGLGENLISTSPROC glad_glGenLists = NULL; +PFNGLGENQUERIESPROC glad_glGenQueries = NULL; +PFNGLGENTEXTURESPROC glad_glGenTextures = NULL; +PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib = NULL; +PFNGLGETACTIVEUNIFORMPROC glad_glGetActiveUniform = NULL; +PFNGLGETATTACHEDSHADERSPROC glad_glGetAttachedShaders = NULL; +PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation = NULL; +PFNGLGETBOOLEANVPROC glad_glGetBooleanv = NULL; +PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv = NULL; +PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv = NULL; +PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData = NULL; +PFNGLGETCLIPPLANEPROC glad_glGetClipPlane = NULL; +PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage = NULL; +PFNGLGETDOUBLEVPROC glad_glGetDoublev = NULL; +PFNGLGETERRORPROC glad_glGetError = NULL; +PFNGLGETFLOATVPROC glad_glGetFloatv = NULL; +PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL; +PFNGLGETLIGHTFVPROC glad_glGetLightfv = NULL; +PFNGLGETLIGHTIVPROC glad_glGetLightiv = NULL; +PFNGLGETMAPDVPROC glad_glGetMapdv = NULL; +PFNGLGETMAPFVPROC glad_glGetMapfv = NULL; +PFNGLGETMAPIVPROC glad_glGetMapiv = NULL; +PFNGLGETMATERIALFVPROC glad_glGetMaterialfv = NULL; +PFNGLGETMATERIALIVPROC glad_glGetMaterialiv = NULL; +PFNGLGETPIXELMAPFVPROC glad_glGetPixelMapfv = NULL; +PFNGLGETPIXELMAPUIVPROC glad_glGetPixelMapuiv = NULL; +PFNGLGETPIXELMAPUSVPROC glad_glGetPixelMapusv = NULL; +PFNGLGETPOINTERVPROC glad_glGetPointerv = NULL; +PFNGLGETPOLYGONSTIPPLEPROC glad_glGetPolygonStipple = NULL; +PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog = NULL; +PFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL; +PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv = NULL; +PFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv = NULL; +PFNGLGETQUERYIVPROC glad_glGetQueryiv = NULL; +PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog = NULL; +PFNGLGETSHADERSOURCEPROC glad_glGetShaderSource = NULL; +PFNGLGETSHADERIVPROC glad_glGetShaderiv = NULL; +PFNGLGETSTRINGPROC glad_glGetString = NULL; +PFNGLGETTEXENVFVPROC glad_glGetTexEnvfv = NULL; +PFNGLGETTEXENVIVPROC glad_glGetTexEnviv = NULL; +PFNGLGETTEXGENDVPROC glad_glGetTexGendv = NULL; +PFNGLGETTEXGENFVPROC glad_glGetTexGenfv = NULL; +PFNGLGETTEXGENIVPROC glad_glGetTexGeniv = NULL; +PFNGLGETTEXIMAGEPROC glad_glGetTexImage = NULL; +PFNGLGETTEXLEVELPARAMETERFVPROC glad_glGetTexLevelParameterfv = NULL; +PFNGLGETTEXLEVELPARAMETERIVPROC glad_glGetTexLevelParameteriv = NULL; +PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv = NULL; +PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv = NULL; +PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation = NULL; +PFNGLGETUNIFORMFVPROC glad_glGetUniformfv = NULL; +PFNGLGETUNIFORMIVPROC glad_glGetUniformiv = NULL; +PFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv = NULL; +PFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv = NULL; +PFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv = NULL; +PFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv = NULL; +PFNGLHINTPROC glad_glHint = NULL; +PFNGLINDEXMASKPROC glad_glIndexMask = NULL; +PFNGLINDEXPOINTERPROC glad_glIndexPointer = NULL; +PFNGLINDEXDPROC glad_glIndexd = NULL; +PFNGLINDEXDVPROC glad_glIndexdv = NULL; +PFNGLINDEXFPROC glad_glIndexf = NULL; +PFNGLINDEXFVPROC glad_glIndexfv = NULL; +PFNGLINDEXIPROC glad_glIndexi = NULL; +PFNGLINDEXIVPROC glad_glIndexiv = NULL; +PFNGLINDEXSPROC glad_glIndexs = NULL; +PFNGLINDEXSVPROC glad_glIndexsv = NULL; +PFNGLINDEXUBPROC glad_glIndexub = NULL; +PFNGLINDEXUBVPROC glad_glIndexubv = NULL; +PFNGLINITNAMESPROC glad_glInitNames = NULL; +PFNGLINTERLEAVEDARRAYSPROC glad_glInterleavedArrays = NULL; +PFNGLISBUFFERPROC glad_glIsBuffer = NULL; +PFNGLISENABLEDPROC glad_glIsEnabled = NULL; +PFNGLISLISTPROC glad_glIsList = NULL; +PFNGLISPROGRAMPROC glad_glIsProgram = NULL; +PFNGLISQUERYPROC glad_glIsQuery = NULL; +PFNGLISSHADERPROC glad_glIsShader = NULL; +PFNGLISTEXTUREPROC glad_glIsTexture = NULL; +PFNGLLIGHTMODELFPROC glad_glLightModelf = NULL; +PFNGLLIGHTMODELFVPROC glad_glLightModelfv = NULL; +PFNGLLIGHTMODELIPROC glad_glLightModeli = NULL; +PFNGLLIGHTMODELIVPROC glad_glLightModeliv = NULL; +PFNGLLIGHTFPROC glad_glLightf = NULL; +PFNGLLIGHTFVPROC glad_glLightfv = NULL; +PFNGLLIGHTIPROC glad_glLighti = NULL; +PFNGLLIGHTIVPROC glad_glLightiv = NULL; +PFNGLLINESTIPPLEPROC glad_glLineStipple = NULL; +PFNGLLINEWIDTHPROC glad_glLineWidth = NULL; +PFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL; +PFNGLLISTBASEPROC glad_glListBase = NULL; +PFNGLLOADIDENTITYPROC glad_glLoadIdentity = NULL; +PFNGLLOADMATRIXDPROC glad_glLoadMatrixd = NULL; +PFNGLLOADMATRIXFPROC glad_glLoadMatrixf = NULL; +PFNGLLOADNAMEPROC glad_glLoadName = NULL; +PFNGLLOADTRANSPOSEMATRIXDPROC glad_glLoadTransposeMatrixd = NULL; +PFNGLLOADTRANSPOSEMATRIXFPROC glad_glLoadTransposeMatrixf = NULL; +PFNGLLOGICOPPROC glad_glLogicOp = NULL; +PFNGLMAP1DPROC glad_glMap1d = NULL; +PFNGLMAP1FPROC glad_glMap1f = NULL; +PFNGLMAP2DPROC glad_glMap2d = NULL; +PFNGLMAP2FPROC glad_glMap2f = NULL; +PFNGLMAPBUFFERPROC glad_glMapBuffer = NULL; +PFNGLMAPGRID1DPROC glad_glMapGrid1d = NULL; +PFNGLMAPGRID1FPROC glad_glMapGrid1f = NULL; +PFNGLMAPGRID2DPROC glad_glMapGrid2d = NULL; +PFNGLMAPGRID2FPROC glad_glMapGrid2f = NULL; +PFNGLMATERIALFPROC glad_glMaterialf = NULL; +PFNGLMATERIALFVPROC glad_glMaterialfv = NULL; +PFNGLMATERIALIPROC glad_glMateriali = NULL; +PFNGLMATERIALIVPROC glad_glMaterialiv = NULL; +PFNGLMATRIXMODEPROC glad_glMatrixMode = NULL; +PFNGLMULTMATRIXDPROC glad_glMultMatrixd = NULL; +PFNGLMULTMATRIXFPROC glad_glMultMatrixf = NULL; +PFNGLMULTTRANSPOSEMATRIXDPROC glad_glMultTransposeMatrixd = NULL; +PFNGLMULTTRANSPOSEMATRIXFPROC glad_glMultTransposeMatrixf = NULL; +PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays = NULL; +PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements = NULL; +PFNGLMULTITEXCOORD1DPROC glad_glMultiTexCoord1d = NULL; +PFNGLMULTITEXCOORD1DVPROC glad_glMultiTexCoord1dv = NULL; +PFNGLMULTITEXCOORD1FPROC glad_glMultiTexCoord1f = NULL; +PFNGLMULTITEXCOORD1FVPROC glad_glMultiTexCoord1fv = NULL; +PFNGLMULTITEXCOORD1IPROC glad_glMultiTexCoord1i = NULL; +PFNGLMULTITEXCOORD1IVPROC glad_glMultiTexCoord1iv = NULL; +PFNGLMULTITEXCOORD1SPROC glad_glMultiTexCoord1s = NULL; +PFNGLMULTITEXCOORD1SVPROC glad_glMultiTexCoord1sv = NULL; +PFNGLMULTITEXCOORD2DPROC glad_glMultiTexCoord2d = NULL; +PFNGLMULTITEXCOORD2DVPROC glad_glMultiTexCoord2dv = NULL; +PFNGLMULTITEXCOORD2FPROC glad_glMultiTexCoord2f = NULL; +PFNGLMULTITEXCOORD2FVPROC glad_glMultiTexCoord2fv = NULL; +PFNGLMULTITEXCOORD2IPROC glad_glMultiTexCoord2i = NULL; +PFNGLMULTITEXCOORD2IVPROC glad_glMultiTexCoord2iv = NULL; +PFNGLMULTITEXCOORD2SPROC glad_glMultiTexCoord2s = NULL; +PFNGLMULTITEXCOORD2SVPROC glad_glMultiTexCoord2sv = NULL; +PFNGLMULTITEXCOORD3DPROC glad_glMultiTexCoord3d = NULL; +PFNGLMULTITEXCOORD3DVPROC glad_glMultiTexCoord3dv = NULL; +PFNGLMULTITEXCOORD3FPROC glad_glMultiTexCoord3f = NULL; +PFNGLMULTITEXCOORD3FVPROC glad_glMultiTexCoord3fv = NULL; +PFNGLMULTITEXCOORD3IPROC glad_glMultiTexCoord3i = NULL; +PFNGLMULTITEXCOORD3IVPROC glad_glMultiTexCoord3iv = NULL; +PFNGLMULTITEXCOORD3SPROC glad_glMultiTexCoord3s = NULL; +PFNGLMULTITEXCOORD3SVPROC glad_glMultiTexCoord3sv = NULL; +PFNGLMULTITEXCOORD4DPROC glad_glMultiTexCoord4d = NULL; +PFNGLMULTITEXCOORD4DVPROC glad_glMultiTexCoord4dv = NULL; +PFNGLMULTITEXCOORD4FPROC glad_glMultiTexCoord4f = NULL; +PFNGLMULTITEXCOORD4FVPROC glad_glMultiTexCoord4fv = NULL; +PFNGLMULTITEXCOORD4IPROC glad_glMultiTexCoord4i = NULL; +PFNGLMULTITEXCOORD4IVPROC glad_glMultiTexCoord4iv = NULL; +PFNGLMULTITEXCOORD4SPROC glad_glMultiTexCoord4s = NULL; +PFNGLMULTITEXCOORD4SVPROC glad_glMultiTexCoord4sv = NULL; +PFNGLNEWLISTPROC glad_glNewList = NULL; +PFNGLNORMAL3BPROC glad_glNormal3b = NULL; +PFNGLNORMAL3BVPROC glad_glNormal3bv = NULL; +PFNGLNORMAL3DPROC glad_glNormal3d = NULL; +PFNGLNORMAL3DVPROC glad_glNormal3dv = NULL; +PFNGLNORMAL3FPROC glad_glNormal3f = NULL; +PFNGLNORMAL3FVPROC glad_glNormal3fv = NULL; +PFNGLNORMAL3IPROC glad_glNormal3i = NULL; +PFNGLNORMAL3IVPROC glad_glNormal3iv = NULL; +PFNGLNORMAL3SPROC glad_glNormal3s = NULL; +PFNGLNORMAL3SVPROC glad_glNormal3sv = NULL; +PFNGLNORMALPOINTERPROC glad_glNormalPointer = NULL; +PFNGLORTHOPROC glad_glOrtho = NULL; +PFNGLPASSTHROUGHPROC glad_glPassThrough = NULL; +PFNGLPIXELMAPFVPROC glad_glPixelMapfv = NULL; +PFNGLPIXELMAPUIVPROC glad_glPixelMapuiv = NULL; +PFNGLPIXELMAPUSVPROC glad_glPixelMapusv = NULL; +PFNGLPIXELSTOREFPROC glad_glPixelStoref = NULL; +PFNGLPIXELSTOREIPROC glad_glPixelStorei = NULL; +PFNGLPIXELTRANSFERFPROC glad_glPixelTransferf = NULL; +PFNGLPIXELTRANSFERIPROC glad_glPixelTransferi = NULL; +PFNGLPIXELZOOMPROC glad_glPixelZoom = NULL; +PFNGLPOINTPARAMETERFPROC glad_glPointParameterf = NULL; +PFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv = NULL; +PFNGLPOINTPARAMETERIPROC glad_glPointParameteri = NULL; +PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv = NULL; +PFNGLPOINTSIZEPROC glad_glPointSize = NULL; +PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL; +PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset = NULL; +PFNGLPOLYGONSTIPPLEPROC glad_glPolygonStipple = NULL; +PFNGLPOPATTRIBPROC glad_glPopAttrib = NULL; +PFNGLPOPCLIENTATTRIBPROC glad_glPopClientAttrib = NULL; +PFNGLPOPMATRIXPROC glad_glPopMatrix = NULL; +PFNGLPOPNAMEPROC glad_glPopName = NULL; +PFNGLPRIORITIZETEXTURESPROC glad_glPrioritizeTextures = NULL; +PFNGLPUSHATTRIBPROC glad_glPushAttrib = NULL; +PFNGLPUSHCLIENTATTRIBPROC glad_glPushClientAttrib = NULL; +PFNGLPUSHMATRIXPROC glad_glPushMatrix = NULL; +PFNGLPUSHNAMEPROC glad_glPushName = NULL; +PFNGLRASTERPOS2DPROC glad_glRasterPos2d = NULL; +PFNGLRASTERPOS2DVPROC glad_glRasterPos2dv = NULL; +PFNGLRASTERPOS2FPROC glad_glRasterPos2f = NULL; +PFNGLRASTERPOS2FVPROC glad_glRasterPos2fv = NULL; +PFNGLRASTERPOS2IPROC glad_glRasterPos2i = NULL; +PFNGLRASTERPOS2IVPROC glad_glRasterPos2iv = NULL; +PFNGLRASTERPOS2SPROC glad_glRasterPos2s = NULL; +PFNGLRASTERPOS2SVPROC glad_glRasterPos2sv = NULL; +PFNGLRASTERPOS3DPROC glad_glRasterPos3d = NULL; +PFNGLRASTERPOS3DVPROC glad_glRasterPos3dv = NULL; +PFNGLRASTERPOS3FPROC glad_glRasterPos3f = NULL; +PFNGLRASTERPOS3FVPROC glad_glRasterPos3fv = NULL; +PFNGLRASTERPOS3IPROC glad_glRasterPos3i = NULL; +PFNGLRASTERPOS3IVPROC glad_glRasterPos3iv = NULL; +PFNGLRASTERPOS3SPROC glad_glRasterPos3s = NULL; +PFNGLRASTERPOS3SVPROC glad_glRasterPos3sv = NULL; +PFNGLRASTERPOS4DPROC glad_glRasterPos4d = NULL; +PFNGLRASTERPOS4DVPROC glad_glRasterPos4dv = NULL; +PFNGLRASTERPOS4FPROC glad_glRasterPos4f = NULL; +PFNGLRASTERPOS4FVPROC glad_glRasterPos4fv = NULL; +PFNGLRASTERPOS4IPROC glad_glRasterPos4i = NULL; +PFNGLRASTERPOS4IVPROC glad_glRasterPos4iv = NULL; +PFNGLRASTERPOS4SPROC glad_glRasterPos4s = NULL; +PFNGLRASTERPOS4SVPROC glad_glRasterPos4sv = NULL; +PFNGLREADBUFFERPROC glad_glReadBuffer = NULL; +PFNGLREADPIXELSPROC glad_glReadPixels = NULL; +PFNGLRECTDPROC glad_glRectd = NULL; +PFNGLRECTDVPROC glad_glRectdv = NULL; +PFNGLRECTFPROC glad_glRectf = NULL; +PFNGLRECTFVPROC glad_glRectfv = NULL; +PFNGLRECTIPROC glad_glRecti = NULL; +PFNGLRECTIVPROC glad_glRectiv = NULL; +PFNGLRECTSPROC glad_glRects = NULL; +PFNGLRECTSVPROC glad_glRectsv = NULL; +PFNGLRENDERMODEPROC glad_glRenderMode = NULL; +PFNGLROTATEDPROC glad_glRotated = NULL; +PFNGLROTATEFPROC glad_glRotatef = NULL; +PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage = NULL; +PFNGLSCALEDPROC glad_glScaled = NULL; +PFNGLSCALEFPROC glad_glScalef = NULL; +PFNGLSCISSORPROC glad_glScissor = NULL; +PFNGLSECONDARYCOLOR3BPROC glad_glSecondaryColor3b = NULL; +PFNGLSECONDARYCOLOR3BVPROC glad_glSecondaryColor3bv = NULL; +PFNGLSECONDARYCOLOR3DPROC glad_glSecondaryColor3d = NULL; +PFNGLSECONDARYCOLOR3DVPROC glad_glSecondaryColor3dv = NULL; +PFNGLSECONDARYCOLOR3FPROC glad_glSecondaryColor3f = NULL; +PFNGLSECONDARYCOLOR3FVPROC glad_glSecondaryColor3fv = NULL; +PFNGLSECONDARYCOLOR3IPROC glad_glSecondaryColor3i = NULL; +PFNGLSECONDARYCOLOR3IVPROC glad_glSecondaryColor3iv = NULL; +PFNGLSECONDARYCOLOR3SPROC glad_glSecondaryColor3s = NULL; +PFNGLSECONDARYCOLOR3SVPROC glad_glSecondaryColor3sv = NULL; +PFNGLSECONDARYCOLOR3UBPROC glad_glSecondaryColor3ub = NULL; +PFNGLSECONDARYCOLOR3UBVPROC glad_glSecondaryColor3ubv = NULL; +PFNGLSECONDARYCOLOR3UIPROC glad_glSecondaryColor3ui = NULL; +PFNGLSECONDARYCOLOR3UIVPROC glad_glSecondaryColor3uiv = NULL; +PFNGLSECONDARYCOLOR3USPROC glad_glSecondaryColor3us = NULL; +PFNGLSECONDARYCOLOR3USVPROC glad_glSecondaryColor3usv = NULL; +PFNGLSECONDARYCOLORPOINTERPROC glad_glSecondaryColorPointer = NULL; +PFNGLSELECTBUFFERPROC glad_glSelectBuffer = NULL; +PFNGLSHADEMODELPROC glad_glShadeModel = NULL; +PFNGLSHADERSOURCEPROC glad_glShaderSource = NULL; +PFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL; +PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL; +PFNGLSTENCILMASKPROC glad_glStencilMask = NULL; +PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate = NULL; +PFNGLSTENCILOPPROC glad_glStencilOp = NULL; +PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate = NULL; +PFNGLTEXCOORD1DPROC glad_glTexCoord1d = NULL; +PFNGLTEXCOORD1DVPROC glad_glTexCoord1dv = NULL; +PFNGLTEXCOORD1FPROC glad_glTexCoord1f = NULL; +PFNGLTEXCOORD1FVPROC glad_glTexCoord1fv = NULL; +PFNGLTEXCOORD1IPROC glad_glTexCoord1i = NULL; +PFNGLTEXCOORD1IVPROC glad_glTexCoord1iv = NULL; +PFNGLTEXCOORD1SPROC glad_glTexCoord1s = NULL; +PFNGLTEXCOORD1SVPROC glad_glTexCoord1sv = NULL; +PFNGLTEXCOORD2DPROC glad_glTexCoord2d = NULL; +PFNGLTEXCOORD2DVPROC glad_glTexCoord2dv = NULL; +PFNGLTEXCOORD2FPROC glad_glTexCoord2f = NULL; +PFNGLTEXCOORD2FVPROC glad_glTexCoord2fv = NULL; +PFNGLTEXCOORD2IPROC glad_glTexCoord2i = NULL; +PFNGLTEXCOORD2IVPROC glad_glTexCoord2iv = NULL; +PFNGLTEXCOORD2SPROC glad_glTexCoord2s = NULL; +PFNGLTEXCOORD2SVPROC glad_glTexCoord2sv = NULL; +PFNGLTEXCOORD3DPROC glad_glTexCoord3d = NULL; +PFNGLTEXCOORD3DVPROC glad_glTexCoord3dv = NULL; +PFNGLTEXCOORD3FPROC glad_glTexCoord3f = NULL; +PFNGLTEXCOORD3FVPROC glad_glTexCoord3fv = NULL; +PFNGLTEXCOORD3IPROC glad_glTexCoord3i = NULL; +PFNGLTEXCOORD3IVPROC glad_glTexCoord3iv = NULL; +PFNGLTEXCOORD3SPROC glad_glTexCoord3s = NULL; +PFNGLTEXCOORD3SVPROC glad_glTexCoord3sv = NULL; +PFNGLTEXCOORD4DPROC glad_glTexCoord4d = NULL; +PFNGLTEXCOORD4DVPROC glad_glTexCoord4dv = NULL; +PFNGLTEXCOORD4FPROC glad_glTexCoord4f = NULL; +PFNGLTEXCOORD4FVPROC glad_glTexCoord4fv = NULL; +PFNGLTEXCOORD4IPROC glad_glTexCoord4i = NULL; +PFNGLTEXCOORD4IVPROC glad_glTexCoord4iv = NULL; +PFNGLTEXCOORD4SPROC glad_glTexCoord4s = NULL; +PFNGLTEXCOORD4SVPROC glad_glTexCoord4sv = NULL; +PFNGLTEXCOORDPOINTERPROC glad_glTexCoordPointer = NULL; +PFNGLTEXENVFPROC glad_glTexEnvf = NULL; +PFNGLTEXENVFVPROC glad_glTexEnvfv = NULL; +PFNGLTEXENVIPROC glad_glTexEnvi = NULL; +PFNGLTEXENVIVPROC glad_glTexEnviv = NULL; +PFNGLTEXGENDPROC glad_glTexGend = NULL; +PFNGLTEXGENDVPROC glad_glTexGendv = NULL; +PFNGLTEXGENFPROC glad_glTexGenf = NULL; +PFNGLTEXGENFVPROC glad_glTexGenfv = NULL; +PFNGLTEXGENIPROC glad_glTexGeni = NULL; +PFNGLTEXGENIVPROC glad_glTexGeniv = NULL; +PFNGLTEXIMAGE1DPROC glad_glTexImage1D = NULL; +PFNGLTEXIMAGE2DPROC glad_glTexImage2D = NULL; +PFNGLTEXIMAGE3DPROC glad_glTexImage3D = NULL; +PFNGLTEXPARAMETERFPROC glad_glTexParameterf = NULL; +PFNGLTEXPARAMETERFVPROC glad_glTexParameterfv = NULL; +PFNGLTEXPARAMETERIPROC glad_glTexParameteri = NULL; +PFNGLTEXPARAMETERIVPROC glad_glTexParameteriv = NULL; +PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D = NULL; +PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D = NULL; +PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D = NULL; +PFNGLTRANSLATEDPROC glad_glTranslated = NULL; +PFNGLTRANSLATEFPROC glad_glTranslatef = NULL; +PFNGLUNIFORM1FPROC glad_glUniform1f = NULL; +PFNGLUNIFORM1FVPROC glad_glUniform1fv = NULL; +PFNGLUNIFORM1IPROC glad_glUniform1i = NULL; +PFNGLUNIFORM1IVPROC glad_glUniform1iv = NULL; +PFNGLUNIFORM2FPROC glad_glUniform2f = NULL; +PFNGLUNIFORM2FVPROC glad_glUniform2fv = NULL; +PFNGLUNIFORM2IPROC glad_glUniform2i = NULL; +PFNGLUNIFORM2IVPROC glad_glUniform2iv = NULL; +PFNGLUNIFORM3FPROC glad_glUniform3f = NULL; +PFNGLUNIFORM3FVPROC glad_glUniform3fv = NULL; +PFNGLUNIFORM3IPROC glad_glUniform3i = NULL; +PFNGLUNIFORM3IVPROC glad_glUniform3iv = NULL; +PFNGLUNIFORM4FPROC glad_glUniform4f = NULL; +PFNGLUNIFORM4FVPROC glad_glUniform4fv = NULL; +PFNGLUNIFORM4IPROC glad_glUniform4i = NULL; +PFNGLUNIFORM4IVPROC glad_glUniform4iv = NULL; +PFNGLUNIFORMMATRIX2FVPROC glad_glUniformMatrix2fv = NULL; +PFNGLUNIFORMMATRIX2X3FVPROC glad_glUniformMatrix2x3fv = NULL; +PFNGLUNIFORMMATRIX2X4FVPROC glad_glUniformMatrix2x4fv = NULL; +PFNGLUNIFORMMATRIX3FVPROC glad_glUniformMatrix3fv = NULL; +PFNGLUNIFORMMATRIX3X2FVPROC glad_glUniformMatrix3x2fv = NULL; +PFNGLUNIFORMMATRIX3X4FVPROC glad_glUniformMatrix3x4fv = NULL; +PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv = NULL; +PFNGLUNIFORMMATRIX4X2FVPROC glad_glUniformMatrix4x2fv = NULL; +PFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv = NULL; +PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer = NULL; +PFNGLUSEPROGRAMPROC glad_glUseProgram = NULL; +PFNGLVALIDATEPROGRAMPROC glad_glValidateProgram = NULL; +PFNGLVERTEX2DPROC glad_glVertex2d = NULL; +PFNGLVERTEX2DVPROC glad_glVertex2dv = NULL; +PFNGLVERTEX2FPROC glad_glVertex2f = NULL; +PFNGLVERTEX2FVPROC glad_glVertex2fv = NULL; +PFNGLVERTEX2IPROC glad_glVertex2i = NULL; +PFNGLVERTEX2IVPROC glad_glVertex2iv = NULL; +PFNGLVERTEX2SPROC glad_glVertex2s = NULL; +PFNGLVERTEX2SVPROC glad_glVertex2sv = NULL; +PFNGLVERTEX3DPROC glad_glVertex3d = NULL; +PFNGLVERTEX3DVPROC glad_glVertex3dv = NULL; +PFNGLVERTEX3FPROC glad_glVertex3f = NULL; +PFNGLVERTEX3FVPROC glad_glVertex3fv = NULL; +PFNGLVERTEX3IPROC glad_glVertex3i = NULL; +PFNGLVERTEX3IVPROC glad_glVertex3iv = NULL; +PFNGLVERTEX3SPROC glad_glVertex3s = NULL; +PFNGLVERTEX3SVPROC glad_glVertex3sv = NULL; +PFNGLVERTEX4DPROC glad_glVertex4d = NULL; +PFNGLVERTEX4DVPROC glad_glVertex4dv = NULL; +PFNGLVERTEX4FPROC glad_glVertex4f = NULL; +PFNGLVERTEX4FVPROC glad_glVertex4fv = NULL; +PFNGLVERTEX4IPROC glad_glVertex4i = NULL; +PFNGLVERTEX4IVPROC glad_glVertex4iv = NULL; +PFNGLVERTEX4SPROC glad_glVertex4s = NULL; +PFNGLVERTEX4SVPROC glad_glVertex4sv = NULL; +PFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d = NULL; +PFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv = NULL; +PFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f = NULL; +PFNGLVERTEXATTRIB1FVPROC glad_glVertexAttrib1fv = NULL; +PFNGLVERTEXATTRIB1SPROC glad_glVertexAttrib1s = NULL; +PFNGLVERTEXATTRIB1SVPROC glad_glVertexAttrib1sv = NULL; +PFNGLVERTEXATTRIB2DPROC glad_glVertexAttrib2d = NULL; +PFNGLVERTEXATTRIB2DVPROC glad_glVertexAttrib2dv = NULL; +PFNGLVERTEXATTRIB2FPROC glad_glVertexAttrib2f = NULL; +PFNGLVERTEXATTRIB2FVPROC glad_glVertexAttrib2fv = NULL; +PFNGLVERTEXATTRIB2SPROC glad_glVertexAttrib2s = NULL; +PFNGLVERTEXATTRIB2SVPROC glad_glVertexAttrib2sv = NULL; +PFNGLVERTEXATTRIB3DPROC glad_glVertexAttrib3d = NULL; +PFNGLVERTEXATTRIB3DVPROC glad_glVertexAttrib3dv = NULL; +PFNGLVERTEXATTRIB3FPROC glad_glVertexAttrib3f = NULL; +PFNGLVERTEXATTRIB3FVPROC glad_glVertexAttrib3fv = NULL; +PFNGLVERTEXATTRIB3SPROC glad_glVertexAttrib3s = NULL; +PFNGLVERTEXATTRIB3SVPROC glad_glVertexAttrib3sv = NULL; +PFNGLVERTEXATTRIB4NBVPROC glad_glVertexAttrib4Nbv = NULL; +PFNGLVERTEXATTRIB4NIVPROC glad_glVertexAttrib4Niv = NULL; +PFNGLVERTEXATTRIB4NSVPROC glad_glVertexAttrib4Nsv = NULL; +PFNGLVERTEXATTRIB4NUBPROC glad_glVertexAttrib4Nub = NULL; +PFNGLVERTEXATTRIB4NUBVPROC glad_glVertexAttrib4Nubv = NULL; +PFNGLVERTEXATTRIB4NUIVPROC glad_glVertexAttrib4Nuiv = NULL; +PFNGLVERTEXATTRIB4NUSVPROC glad_glVertexAttrib4Nusv = NULL; +PFNGLVERTEXATTRIB4BVPROC glad_glVertexAttrib4bv = NULL; +PFNGLVERTEXATTRIB4DPROC glad_glVertexAttrib4d = NULL; +PFNGLVERTEXATTRIB4DVPROC glad_glVertexAttrib4dv = NULL; +PFNGLVERTEXATTRIB4FPROC glad_glVertexAttrib4f = NULL; +PFNGLVERTEXATTRIB4FVPROC glad_glVertexAttrib4fv = NULL; +PFNGLVERTEXATTRIB4IVPROC glad_glVertexAttrib4iv = NULL; +PFNGLVERTEXATTRIB4SPROC glad_glVertexAttrib4s = NULL; +PFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv = NULL; +PFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv = NULL; +PFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv = NULL; +PFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv = NULL; +PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer = NULL; +PFNGLVERTEXPOINTERPROC glad_glVertexPointer = NULL; +PFNGLVIEWPORTPROC glad_glViewport = NULL; +PFNGLWINDOWPOS2DPROC glad_glWindowPos2d = NULL; +PFNGLWINDOWPOS2DVPROC glad_glWindowPos2dv = NULL; +PFNGLWINDOWPOS2FPROC glad_glWindowPos2f = NULL; +PFNGLWINDOWPOS2FVPROC glad_glWindowPos2fv = NULL; +PFNGLWINDOWPOS2IPROC glad_glWindowPos2i = NULL; +PFNGLWINDOWPOS2IVPROC glad_glWindowPos2iv = NULL; +PFNGLWINDOWPOS2SPROC glad_glWindowPos2s = NULL; +PFNGLWINDOWPOS2SVPROC glad_glWindowPos2sv = NULL; +PFNGLWINDOWPOS3DPROC glad_glWindowPos3d = NULL; +PFNGLWINDOWPOS3DVPROC glad_glWindowPos3dv = NULL; +PFNGLWINDOWPOS3FPROC glad_glWindowPos3f = NULL; +PFNGLWINDOWPOS3FVPROC glad_glWindowPos3fv = NULL; +PFNGLWINDOWPOS3IPROC glad_glWindowPos3i = NULL; +PFNGLWINDOWPOS3IVPROC glad_glWindowPos3iv = NULL; +PFNGLWINDOWPOS3SPROC glad_glWindowPos3s = NULL; +PFNGLWINDOWPOS3SVPROC glad_glWindowPos3sv = NULL; +static void load_GL_VERSION_1_0(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_0) return; + glad_glCullFace = (PFNGLCULLFACEPROC)load("glCullFace"); + glad_glFrontFace = (PFNGLFRONTFACEPROC)load("glFrontFace"); + glad_glHint = (PFNGLHINTPROC)load("glHint"); + glad_glLineWidth = (PFNGLLINEWIDTHPROC)load("glLineWidth"); + glad_glPointSize = (PFNGLPOINTSIZEPROC)load("glPointSize"); + glad_glPolygonMode = (PFNGLPOLYGONMODEPROC)load("glPolygonMode"); + glad_glScissor = (PFNGLSCISSORPROC)load("glScissor"); + glad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load("glTexParameterf"); + glad_glTexParameterfv = (PFNGLTEXPARAMETERFVPROC)load("glTexParameterfv"); + glad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load("glTexParameteri"); + glad_glTexParameteriv = (PFNGLTEXPARAMETERIVPROC)load("glTexParameteriv"); + glad_glTexImage1D = (PFNGLTEXIMAGE1DPROC)load("glTexImage1D"); + glad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load("glTexImage2D"); + glad_glDrawBuffer = (PFNGLDRAWBUFFERPROC)load("glDrawBuffer"); + glad_glClear = (PFNGLCLEARPROC)load("glClear"); + glad_glClearColor = (PFNGLCLEARCOLORPROC)load("glClearColor"); + glad_glClearStencil = (PFNGLCLEARSTENCILPROC)load("glClearStencil"); + glad_glClearDepth = (PFNGLCLEARDEPTHPROC)load("glClearDepth"); + glad_glStencilMask = (PFNGLSTENCILMASKPROC)load("glStencilMask"); + glad_glColorMask = (PFNGLCOLORMASKPROC)load("glColorMask"); + glad_glDepthMask = (PFNGLDEPTHMASKPROC)load("glDepthMask"); + glad_glDisable = (PFNGLDISABLEPROC)load("glDisable"); + glad_glEnable = (PFNGLENABLEPROC)load("glEnable"); + glad_glFinish = (PFNGLFINISHPROC)load("glFinish"); + glad_glFlush = (PFNGLFLUSHPROC)load("glFlush"); + glad_glBlendFunc = (PFNGLBLENDFUNCPROC)load("glBlendFunc"); + glad_glLogicOp = (PFNGLLOGICOPPROC)load("glLogicOp"); + glad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load("glStencilFunc"); + glad_glStencilOp = (PFNGLSTENCILOPPROC)load("glStencilOp"); + glad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load("glDepthFunc"); + glad_glPixelStoref = (PFNGLPIXELSTOREFPROC)load("glPixelStoref"); + glad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load("glPixelStorei"); + glad_glReadBuffer = (PFNGLREADBUFFERPROC)load("glReadBuffer"); + glad_glReadPixels = (PFNGLREADPIXELSPROC)load("glReadPixels"); + glad_glGetBooleanv = (PFNGLGETBOOLEANVPROC)load("glGetBooleanv"); + glad_glGetDoublev = (PFNGLGETDOUBLEVPROC)load("glGetDoublev"); + glad_glGetError = (PFNGLGETERRORPROC)load("glGetError"); + glad_glGetFloatv = (PFNGLGETFLOATVPROC)load("glGetFloatv"); + glad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load("glGetIntegerv"); + glad_glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + glad_glGetTexImage = (PFNGLGETTEXIMAGEPROC)load("glGetTexImage"); + glad_glGetTexParameterfv = (PFNGLGETTEXPARAMETERFVPROC)load("glGetTexParameterfv"); + glad_glGetTexParameteriv = (PFNGLGETTEXPARAMETERIVPROC)load("glGetTexParameteriv"); + glad_glGetTexLevelParameterfv = (PFNGLGETTEXLEVELPARAMETERFVPROC)load("glGetTexLevelParameterfv"); + glad_glGetTexLevelParameteriv = (PFNGLGETTEXLEVELPARAMETERIVPROC)load("glGetTexLevelParameteriv"); + glad_glIsEnabled = (PFNGLISENABLEDPROC)load("glIsEnabled"); + glad_glDepthRange = (PFNGLDEPTHRANGEPROC)load("glDepthRange"); + glad_glViewport = (PFNGLVIEWPORTPROC)load("glViewport"); + glad_glNewList = (PFNGLNEWLISTPROC)load("glNewList"); + glad_glEndList = (PFNGLENDLISTPROC)load("glEndList"); + glad_glCallList = (PFNGLCALLLISTPROC)load("glCallList"); + glad_glCallLists = (PFNGLCALLLISTSPROC)load("glCallLists"); + glad_glDeleteLists = (PFNGLDELETELISTSPROC)load("glDeleteLists"); + glad_glGenLists = (PFNGLGENLISTSPROC)load("glGenLists"); + glad_glListBase = (PFNGLLISTBASEPROC)load("glListBase"); + glad_glBegin = (PFNGLBEGINPROC)load("glBegin"); + glad_glBitmap = (PFNGLBITMAPPROC)load("glBitmap"); + glad_glColor3b = (PFNGLCOLOR3BPROC)load("glColor3b"); + glad_glColor3bv = (PFNGLCOLOR3BVPROC)load("glColor3bv"); + glad_glColor3d = (PFNGLCOLOR3DPROC)load("glColor3d"); + glad_glColor3dv = (PFNGLCOLOR3DVPROC)load("glColor3dv"); + glad_glColor3f = (PFNGLCOLOR3FPROC)load("glColor3f"); + glad_glColor3fv = (PFNGLCOLOR3FVPROC)load("glColor3fv"); + glad_glColor3i = (PFNGLCOLOR3IPROC)load("glColor3i"); + glad_glColor3iv = (PFNGLCOLOR3IVPROC)load("glColor3iv"); + glad_glColor3s = (PFNGLCOLOR3SPROC)load("glColor3s"); + glad_glColor3sv = (PFNGLCOLOR3SVPROC)load("glColor3sv"); + glad_glColor3ub = (PFNGLCOLOR3UBPROC)load("glColor3ub"); + glad_glColor3ubv = (PFNGLCOLOR3UBVPROC)load("glColor3ubv"); + glad_glColor3ui = (PFNGLCOLOR3UIPROC)load("glColor3ui"); + glad_glColor3uiv = (PFNGLCOLOR3UIVPROC)load("glColor3uiv"); + glad_glColor3us = (PFNGLCOLOR3USPROC)load("glColor3us"); + glad_glColor3usv = (PFNGLCOLOR3USVPROC)load("glColor3usv"); + glad_glColor4b = (PFNGLCOLOR4BPROC)load("glColor4b"); + glad_glColor4bv = (PFNGLCOLOR4BVPROC)load("glColor4bv"); + glad_glColor4d = (PFNGLCOLOR4DPROC)load("glColor4d"); + glad_glColor4dv = (PFNGLCOLOR4DVPROC)load("glColor4dv"); + glad_glColor4f = (PFNGLCOLOR4FPROC)load("glColor4f"); + glad_glColor4fv = (PFNGLCOLOR4FVPROC)load("glColor4fv"); + glad_glColor4i = (PFNGLCOLOR4IPROC)load("glColor4i"); + glad_glColor4iv = (PFNGLCOLOR4IVPROC)load("glColor4iv"); + glad_glColor4s = (PFNGLCOLOR4SPROC)load("glColor4s"); + glad_glColor4sv = (PFNGLCOLOR4SVPROC)load("glColor4sv"); + glad_glColor4ub = (PFNGLCOLOR4UBPROC)load("glColor4ub"); + glad_glColor4ubv = (PFNGLCOLOR4UBVPROC)load("glColor4ubv"); + glad_glColor4ui = (PFNGLCOLOR4UIPROC)load("glColor4ui"); + glad_glColor4uiv = (PFNGLCOLOR4UIVPROC)load("glColor4uiv"); + glad_glColor4us = (PFNGLCOLOR4USPROC)load("glColor4us"); + glad_glColor4usv = (PFNGLCOLOR4USVPROC)load("glColor4usv"); + glad_glEdgeFlag = (PFNGLEDGEFLAGPROC)load("glEdgeFlag"); + glad_glEdgeFlagv = (PFNGLEDGEFLAGVPROC)load("glEdgeFlagv"); + glad_glEnd = (PFNGLENDPROC)load("glEnd"); + glad_glIndexd = (PFNGLINDEXDPROC)load("glIndexd"); + glad_glIndexdv = (PFNGLINDEXDVPROC)load("glIndexdv"); + glad_glIndexf = (PFNGLINDEXFPROC)load("glIndexf"); + glad_glIndexfv = (PFNGLINDEXFVPROC)load("glIndexfv"); + glad_glIndexi = (PFNGLINDEXIPROC)load("glIndexi"); + glad_glIndexiv = (PFNGLINDEXIVPROC)load("glIndexiv"); + glad_glIndexs = (PFNGLINDEXSPROC)load("glIndexs"); + glad_glIndexsv = (PFNGLINDEXSVPROC)load("glIndexsv"); + glad_glNormal3b = (PFNGLNORMAL3BPROC)load("glNormal3b"); + glad_glNormal3bv = (PFNGLNORMAL3BVPROC)load("glNormal3bv"); + glad_glNormal3d = (PFNGLNORMAL3DPROC)load("glNormal3d"); + glad_glNormal3dv = (PFNGLNORMAL3DVPROC)load("glNormal3dv"); + glad_glNormal3f = (PFNGLNORMAL3FPROC)load("glNormal3f"); + glad_glNormal3fv = (PFNGLNORMAL3FVPROC)load("glNormal3fv"); + glad_glNormal3i = (PFNGLNORMAL3IPROC)load("glNormal3i"); + glad_glNormal3iv = (PFNGLNORMAL3IVPROC)load("glNormal3iv"); + glad_glNormal3s = (PFNGLNORMAL3SPROC)load("glNormal3s"); + glad_glNormal3sv = (PFNGLNORMAL3SVPROC)load("glNormal3sv"); + glad_glRasterPos2d = (PFNGLRASTERPOS2DPROC)load("glRasterPos2d"); + glad_glRasterPos2dv = (PFNGLRASTERPOS2DVPROC)load("glRasterPos2dv"); + glad_glRasterPos2f = (PFNGLRASTERPOS2FPROC)load("glRasterPos2f"); + glad_glRasterPos2fv = (PFNGLRASTERPOS2FVPROC)load("glRasterPos2fv"); + glad_glRasterPos2i = (PFNGLRASTERPOS2IPROC)load("glRasterPos2i"); + glad_glRasterPos2iv = (PFNGLRASTERPOS2IVPROC)load("glRasterPos2iv"); + glad_glRasterPos2s = (PFNGLRASTERPOS2SPROC)load("glRasterPos2s"); + glad_glRasterPos2sv = (PFNGLRASTERPOS2SVPROC)load("glRasterPos2sv"); + glad_glRasterPos3d = (PFNGLRASTERPOS3DPROC)load("glRasterPos3d"); + glad_glRasterPos3dv = (PFNGLRASTERPOS3DVPROC)load("glRasterPos3dv"); + glad_glRasterPos3f = (PFNGLRASTERPOS3FPROC)load("glRasterPos3f"); + glad_glRasterPos3fv = (PFNGLRASTERPOS3FVPROC)load("glRasterPos3fv"); + glad_glRasterPos3i = (PFNGLRASTERPOS3IPROC)load("glRasterPos3i"); + glad_glRasterPos3iv = (PFNGLRASTERPOS3IVPROC)load("glRasterPos3iv"); + glad_glRasterPos3s = (PFNGLRASTERPOS3SPROC)load("glRasterPos3s"); + glad_glRasterPos3sv = (PFNGLRASTERPOS3SVPROC)load("glRasterPos3sv"); + glad_glRasterPos4d = (PFNGLRASTERPOS4DPROC)load("glRasterPos4d"); + glad_glRasterPos4dv = (PFNGLRASTERPOS4DVPROC)load("glRasterPos4dv"); + glad_glRasterPos4f = (PFNGLRASTERPOS4FPROC)load("glRasterPos4f"); + glad_glRasterPos4fv = (PFNGLRASTERPOS4FVPROC)load("glRasterPos4fv"); + glad_glRasterPos4i = (PFNGLRASTERPOS4IPROC)load("glRasterPos4i"); + glad_glRasterPos4iv = (PFNGLRASTERPOS4IVPROC)load("glRasterPos4iv"); + glad_glRasterPos4s = (PFNGLRASTERPOS4SPROC)load("glRasterPos4s"); + glad_glRasterPos4sv = (PFNGLRASTERPOS4SVPROC)load("glRasterPos4sv"); + glad_glRectd = (PFNGLRECTDPROC)load("glRectd"); + glad_glRectdv = (PFNGLRECTDVPROC)load("glRectdv"); + glad_glRectf = (PFNGLRECTFPROC)load("glRectf"); + glad_glRectfv = (PFNGLRECTFVPROC)load("glRectfv"); + glad_glRecti = (PFNGLRECTIPROC)load("glRecti"); + glad_glRectiv = (PFNGLRECTIVPROC)load("glRectiv"); + glad_glRects = (PFNGLRECTSPROC)load("glRects"); + glad_glRectsv = (PFNGLRECTSVPROC)load("glRectsv"); + glad_glTexCoord1d = (PFNGLTEXCOORD1DPROC)load("glTexCoord1d"); + glad_glTexCoord1dv = (PFNGLTEXCOORD1DVPROC)load("glTexCoord1dv"); + glad_glTexCoord1f = (PFNGLTEXCOORD1FPROC)load("glTexCoord1f"); + glad_glTexCoord1fv = (PFNGLTEXCOORD1FVPROC)load("glTexCoord1fv"); + glad_glTexCoord1i = (PFNGLTEXCOORD1IPROC)load("glTexCoord1i"); + glad_glTexCoord1iv = (PFNGLTEXCOORD1IVPROC)load("glTexCoord1iv"); + glad_glTexCoord1s = (PFNGLTEXCOORD1SPROC)load("glTexCoord1s"); + glad_glTexCoord1sv = (PFNGLTEXCOORD1SVPROC)load("glTexCoord1sv"); + glad_glTexCoord2d = (PFNGLTEXCOORD2DPROC)load("glTexCoord2d"); + glad_glTexCoord2dv = (PFNGLTEXCOORD2DVPROC)load("glTexCoord2dv"); + glad_glTexCoord2f = (PFNGLTEXCOORD2FPROC)load("glTexCoord2f"); + glad_glTexCoord2fv = (PFNGLTEXCOORD2FVPROC)load("glTexCoord2fv"); + glad_glTexCoord2i = (PFNGLTEXCOORD2IPROC)load("glTexCoord2i"); + glad_glTexCoord2iv = (PFNGLTEXCOORD2IVPROC)load("glTexCoord2iv"); + glad_glTexCoord2s = (PFNGLTEXCOORD2SPROC)load("glTexCoord2s"); + glad_glTexCoord2sv = (PFNGLTEXCOORD2SVPROC)load("glTexCoord2sv"); + glad_glTexCoord3d = (PFNGLTEXCOORD3DPROC)load("glTexCoord3d"); + glad_glTexCoord3dv = (PFNGLTEXCOORD3DVPROC)load("glTexCoord3dv"); + glad_glTexCoord3f = (PFNGLTEXCOORD3FPROC)load("glTexCoord3f"); + glad_glTexCoord3fv = (PFNGLTEXCOORD3FVPROC)load("glTexCoord3fv"); + glad_glTexCoord3i = (PFNGLTEXCOORD3IPROC)load("glTexCoord3i"); + glad_glTexCoord3iv = (PFNGLTEXCOORD3IVPROC)load("glTexCoord3iv"); + glad_glTexCoord3s = (PFNGLTEXCOORD3SPROC)load("glTexCoord3s"); + glad_glTexCoord3sv = (PFNGLTEXCOORD3SVPROC)load("glTexCoord3sv"); + glad_glTexCoord4d = (PFNGLTEXCOORD4DPROC)load("glTexCoord4d"); + glad_glTexCoord4dv = (PFNGLTEXCOORD4DVPROC)load("glTexCoord4dv"); + glad_glTexCoord4f = (PFNGLTEXCOORD4FPROC)load("glTexCoord4f"); + glad_glTexCoord4fv = (PFNGLTEXCOORD4FVPROC)load("glTexCoord4fv"); + glad_glTexCoord4i = (PFNGLTEXCOORD4IPROC)load("glTexCoord4i"); + glad_glTexCoord4iv = (PFNGLTEXCOORD4IVPROC)load("glTexCoord4iv"); + glad_glTexCoord4s = (PFNGLTEXCOORD4SPROC)load("glTexCoord4s"); + glad_glTexCoord4sv = (PFNGLTEXCOORD4SVPROC)load("glTexCoord4sv"); + glad_glVertex2d = (PFNGLVERTEX2DPROC)load("glVertex2d"); + glad_glVertex2dv = (PFNGLVERTEX2DVPROC)load("glVertex2dv"); + glad_glVertex2f = (PFNGLVERTEX2FPROC)load("glVertex2f"); + glad_glVertex2fv = (PFNGLVERTEX2FVPROC)load("glVertex2fv"); + glad_glVertex2i = (PFNGLVERTEX2IPROC)load("glVertex2i"); + glad_glVertex2iv = (PFNGLVERTEX2IVPROC)load("glVertex2iv"); + glad_glVertex2s = (PFNGLVERTEX2SPROC)load("glVertex2s"); + glad_glVertex2sv = (PFNGLVERTEX2SVPROC)load("glVertex2sv"); + glad_glVertex3d = (PFNGLVERTEX3DPROC)load("glVertex3d"); + glad_glVertex3dv = (PFNGLVERTEX3DVPROC)load("glVertex3dv"); + glad_glVertex3f = (PFNGLVERTEX3FPROC)load("glVertex3f"); + glad_glVertex3fv = (PFNGLVERTEX3FVPROC)load("glVertex3fv"); + glad_glVertex3i = (PFNGLVERTEX3IPROC)load("glVertex3i"); + glad_glVertex3iv = (PFNGLVERTEX3IVPROC)load("glVertex3iv"); + glad_glVertex3s = (PFNGLVERTEX3SPROC)load("glVertex3s"); + glad_glVertex3sv = (PFNGLVERTEX3SVPROC)load("glVertex3sv"); + glad_glVertex4d = (PFNGLVERTEX4DPROC)load("glVertex4d"); + glad_glVertex4dv = (PFNGLVERTEX4DVPROC)load("glVertex4dv"); + glad_glVertex4f = (PFNGLVERTEX4FPROC)load("glVertex4f"); + glad_glVertex4fv = (PFNGLVERTEX4FVPROC)load("glVertex4fv"); + glad_glVertex4i = (PFNGLVERTEX4IPROC)load("glVertex4i"); + glad_glVertex4iv = (PFNGLVERTEX4IVPROC)load("glVertex4iv"); + glad_glVertex4s = (PFNGLVERTEX4SPROC)load("glVertex4s"); + glad_glVertex4sv = (PFNGLVERTEX4SVPROC)load("glVertex4sv"); + glad_glClipPlane = (PFNGLCLIPPLANEPROC)load("glClipPlane"); + glad_glColorMaterial = (PFNGLCOLORMATERIALPROC)load("glColorMaterial"); + glad_glFogf = (PFNGLFOGFPROC)load("glFogf"); + glad_glFogfv = (PFNGLFOGFVPROC)load("glFogfv"); + glad_glFogi = (PFNGLFOGIPROC)load("glFogi"); + glad_glFogiv = (PFNGLFOGIVPROC)load("glFogiv"); + glad_glLightf = (PFNGLLIGHTFPROC)load("glLightf"); + glad_glLightfv = (PFNGLLIGHTFVPROC)load("glLightfv"); + glad_glLighti = (PFNGLLIGHTIPROC)load("glLighti"); + glad_glLightiv = (PFNGLLIGHTIVPROC)load("glLightiv"); + glad_glLightModelf = (PFNGLLIGHTMODELFPROC)load("glLightModelf"); + glad_glLightModelfv = (PFNGLLIGHTMODELFVPROC)load("glLightModelfv"); + glad_glLightModeli = (PFNGLLIGHTMODELIPROC)load("glLightModeli"); + glad_glLightModeliv = (PFNGLLIGHTMODELIVPROC)load("glLightModeliv"); + glad_glLineStipple = (PFNGLLINESTIPPLEPROC)load("glLineStipple"); + glad_glMaterialf = (PFNGLMATERIALFPROC)load("glMaterialf"); + glad_glMaterialfv = (PFNGLMATERIALFVPROC)load("glMaterialfv"); + glad_glMateriali = (PFNGLMATERIALIPROC)load("glMateriali"); + glad_glMaterialiv = (PFNGLMATERIALIVPROC)load("glMaterialiv"); + glad_glPolygonStipple = (PFNGLPOLYGONSTIPPLEPROC)load("glPolygonStipple"); + glad_glShadeModel = (PFNGLSHADEMODELPROC)load("glShadeModel"); + glad_glTexEnvf = (PFNGLTEXENVFPROC)load("glTexEnvf"); + glad_glTexEnvfv = (PFNGLTEXENVFVPROC)load("glTexEnvfv"); + glad_glTexEnvi = (PFNGLTEXENVIPROC)load("glTexEnvi"); + glad_glTexEnviv = (PFNGLTEXENVIVPROC)load("glTexEnviv"); + glad_glTexGend = (PFNGLTEXGENDPROC)load("glTexGend"); + glad_glTexGendv = (PFNGLTEXGENDVPROC)load("glTexGendv"); + glad_glTexGenf = (PFNGLTEXGENFPROC)load("glTexGenf"); + glad_glTexGenfv = (PFNGLTEXGENFVPROC)load("glTexGenfv"); + glad_glTexGeni = (PFNGLTEXGENIPROC)load("glTexGeni"); + glad_glTexGeniv = (PFNGLTEXGENIVPROC)load("glTexGeniv"); + glad_glFeedbackBuffer = (PFNGLFEEDBACKBUFFERPROC)load("glFeedbackBuffer"); + glad_glSelectBuffer = (PFNGLSELECTBUFFERPROC)load("glSelectBuffer"); + glad_glRenderMode = (PFNGLRENDERMODEPROC)load("glRenderMode"); + glad_glInitNames = (PFNGLINITNAMESPROC)load("glInitNames"); + glad_glLoadName = (PFNGLLOADNAMEPROC)load("glLoadName"); + glad_glPassThrough = (PFNGLPASSTHROUGHPROC)load("glPassThrough"); + glad_glPopName = (PFNGLPOPNAMEPROC)load("glPopName"); + glad_glPushName = (PFNGLPUSHNAMEPROC)load("glPushName"); + glad_glClearAccum = (PFNGLCLEARACCUMPROC)load("glClearAccum"); + glad_glClearIndex = (PFNGLCLEARINDEXPROC)load("glClearIndex"); + glad_glIndexMask = (PFNGLINDEXMASKPROC)load("glIndexMask"); + glad_glAccum = (PFNGLACCUMPROC)load("glAccum"); + glad_glPopAttrib = (PFNGLPOPATTRIBPROC)load("glPopAttrib"); + glad_glPushAttrib = (PFNGLPUSHATTRIBPROC)load("glPushAttrib"); + glad_glMap1d = (PFNGLMAP1DPROC)load("glMap1d"); + glad_glMap1f = (PFNGLMAP1FPROC)load("glMap1f"); + glad_glMap2d = (PFNGLMAP2DPROC)load("glMap2d"); + glad_glMap2f = (PFNGLMAP2FPROC)load("glMap2f"); + glad_glMapGrid1d = (PFNGLMAPGRID1DPROC)load("glMapGrid1d"); + glad_glMapGrid1f = (PFNGLMAPGRID1FPROC)load("glMapGrid1f"); + glad_glMapGrid2d = (PFNGLMAPGRID2DPROC)load("glMapGrid2d"); + glad_glMapGrid2f = (PFNGLMAPGRID2FPROC)load("glMapGrid2f"); + glad_glEvalCoord1d = (PFNGLEVALCOORD1DPROC)load("glEvalCoord1d"); + glad_glEvalCoord1dv = (PFNGLEVALCOORD1DVPROC)load("glEvalCoord1dv"); + glad_glEvalCoord1f = (PFNGLEVALCOORD1FPROC)load("glEvalCoord1f"); + glad_glEvalCoord1fv = (PFNGLEVALCOORD1FVPROC)load("glEvalCoord1fv"); + glad_glEvalCoord2d = (PFNGLEVALCOORD2DPROC)load("glEvalCoord2d"); + glad_glEvalCoord2dv = (PFNGLEVALCOORD2DVPROC)load("glEvalCoord2dv"); + glad_glEvalCoord2f = (PFNGLEVALCOORD2FPROC)load("glEvalCoord2f"); + glad_glEvalCoord2fv = (PFNGLEVALCOORD2FVPROC)load("glEvalCoord2fv"); + glad_glEvalMesh1 = (PFNGLEVALMESH1PROC)load("glEvalMesh1"); + glad_glEvalPoint1 = (PFNGLEVALPOINT1PROC)load("glEvalPoint1"); + glad_glEvalMesh2 = (PFNGLEVALMESH2PROC)load("glEvalMesh2"); + glad_glEvalPoint2 = (PFNGLEVALPOINT2PROC)load("glEvalPoint2"); + glad_glAlphaFunc = (PFNGLALPHAFUNCPROC)load("glAlphaFunc"); + glad_glPixelZoom = (PFNGLPIXELZOOMPROC)load("glPixelZoom"); + glad_glPixelTransferf = (PFNGLPIXELTRANSFERFPROC)load("glPixelTransferf"); + glad_glPixelTransferi = (PFNGLPIXELTRANSFERIPROC)load("glPixelTransferi"); + glad_glPixelMapfv = (PFNGLPIXELMAPFVPROC)load("glPixelMapfv"); + glad_glPixelMapuiv = (PFNGLPIXELMAPUIVPROC)load("glPixelMapuiv"); + glad_glPixelMapusv = (PFNGLPIXELMAPUSVPROC)load("glPixelMapusv"); + glad_glCopyPixels = (PFNGLCOPYPIXELSPROC)load("glCopyPixels"); + glad_glDrawPixels = (PFNGLDRAWPIXELSPROC)load("glDrawPixels"); + glad_glGetClipPlane = (PFNGLGETCLIPPLANEPROC)load("glGetClipPlane"); + glad_glGetLightfv = (PFNGLGETLIGHTFVPROC)load("glGetLightfv"); + glad_glGetLightiv = (PFNGLGETLIGHTIVPROC)load("glGetLightiv"); + glad_glGetMapdv = (PFNGLGETMAPDVPROC)load("glGetMapdv"); + glad_glGetMapfv = (PFNGLGETMAPFVPROC)load("glGetMapfv"); + glad_glGetMapiv = (PFNGLGETMAPIVPROC)load("glGetMapiv"); + glad_glGetMaterialfv = (PFNGLGETMATERIALFVPROC)load("glGetMaterialfv"); + glad_glGetMaterialiv = (PFNGLGETMATERIALIVPROC)load("glGetMaterialiv"); + glad_glGetPixelMapfv = (PFNGLGETPIXELMAPFVPROC)load("glGetPixelMapfv"); + glad_glGetPixelMapuiv = (PFNGLGETPIXELMAPUIVPROC)load("glGetPixelMapuiv"); + glad_glGetPixelMapusv = (PFNGLGETPIXELMAPUSVPROC)load("glGetPixelMapusv"); + glad_glGetPolygonStipple = (PFNGLGETPOLYGONSTIPPLEPROC)load("glGetPolygonStipple"); + glad_glGetTexEnvfv = (PFNGLGETTEXENVFVPROC)load("glGetTexEnvfv"); + glad_glGetTexEnviv = (PFNGLGETTEXENVIVPROC)load("glGetTexEnviv"); + glad_glGetTexGendv = (PFNGLGETTEXGENDVPROC)load("glGetTexGendv"); + glad_glGetTexGenfv = (PFNGLGETTEXGENFVPROC)load("glGetTexGenfv"); + glad_glGetTexGeniv = (PFNGLGETTEXGENIVPROC)load("glGetTexGeniv"); + glad_glIsList = (PFNGLISLISTPROC)load("glIsList"); + glad_glFrustum = (PFNGLFRUSTUMPROC)load("glFrustum"); + glad_glLoadIdentity = (PFNGLLOADIDENTITYPROC)load("glLoadIdentity"); + glad_glLoadMatrixf = (PFNGLLOADMATRIXFPROC)load("glLoadMatrixf"); + glad_glLoadMatrixd = (PFNGLLOADMATRIXDPROC)load("glLoadMatrixd"); + glad_glMatrixMode = (PFNGLMATRIXMODEPROC)load("glMatrixMode"); + glad_glMultMatrixf = (PFNGLMULTMATRIXFPROC)load("glMultMatrixf"); + glad_glMultMatrixd = (PFNGLMULTMATRIXDPROC)load("glMultMatrixd"); + glad_glOrtho = (PFNGLORTHOPROC)load("glOrtho"); + glad_glPopMatrix = (PFNGLPOPMATRIXPROC)load("glPopMatrix"); + glad_glPushMatrix = (PFNGLPUSHMATRIXPROC)load("glPushMatrix"); + glad_glRotated = (PFNGLROTATEDPROC)load("glRotated"); + glad_glRotatef = (PFNGLROTATEFPROC)load("glRotatef"); + glad_glScaled = (PFNGLSCALEDPROC)load("glScaled"); + glad_glScalef = (PFNGLSCALEFPROC)load("glScalef"); + glad_glTranslated = (PFNGLTRANSLATEDPROC)load("glTranslated"); + glad_glTranslatef = (PFNGLTRANSLATEFPROC)load("glTranslatef"); +} +static void load_GL_VERSION_1_1(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_1) return; + glad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load("glDrawArrays"); + glad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load("glDrawElements"); + glad_glGetPointerv = (PFNGLGETPOINTERVPROC)load("glGetPointerv"); + glad_glPolygonOffset = (PFNGLPOLYGONOFFSETPROC)load("glPolygonOffset"); + glad_glCopyTexImage1D = (PFNGLCOPYTEXIMAGE1DPROC)load("glCopyTexImage1D"); + glad_glCopyTexImage2D = (PFNGLCOPYTEXIMAGE2DPROC)load("glCopyTexImage2D"); + glad_glCopyTexSubImage1D = (PFNGLCOPYTEXSUBIMAGE1DPROC)load("glCopyTexSubImage1D"); + glad_glCopyTexSubImage2D = (PFNGLCOPYTEXSUBIMAGE2DPROC)load("glCopyTexSubImage2D"); + glad_glTexSubImage1D = (PFNGLTEXSUBIMAGE1DPROC)load("glTexSubImage1D"); + glad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load("glTexSubImage2D"); + glad_glBindTexture = (PFNGLBINDTEXTUREPROC)load("glBindTexture"); + glad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load("glDeleteTextures"); + glad_glGenTextures = (PFNGLGENTEXTURESPROC)load("glGenTextures"); + glad_glIsTexture = (PFNGLISTEXTUREPROC)load("glIsTexture"); + glad_glArrayElement = (PFNGLARRAYELEMENTPROC)load("glArrayElement"); + glad_glColorPointer = (PFNGLCOLORPOINTERPROC)load("glColorPointer"); + glad_glDisableClientState = (PFNGLDISABLECLIENTSTATEPROC)load("glDisableClientState"); + glad_glEdgeFlagPointer = (PFNGLEDGEFLAGPOINTERPROC)load("glEdgeFlagPointer"); + glad_glEnableClientState = (PFNGLENABLECLIENTSTATEPROC)load("glEnableClientState"); + glad_glIndexPointer = (PFNGLINDEXPOINTERPROC)load("glIndexPointer"); + glad_glInterleavedArrays = (PFNGLINTERLEAVEDARRAYSPROC)load("glInterleavedArrays"); + glad_glNormalPointer = (PFNGLNORMALPOINTERPROC)load("glNormalPointer"); + glad_glTexCoordPointer = (PFNGLTEXCOORDPOINTERPROC)load("glTexCoordPointer"); + glad_glVertexPointer = (PFNGLVERTEXPOINTERPROC)load("glVertexPointer"); + glad_glAreTexturesResident = (PFNGLARETEXTURESRESIDENTPROC)load("glAreTexturesResident"); + glad_glPrioritizeTextures = (PFNGLPRIORITIZETEXTURESPROC)load("glPrioritizeTextures"); + glad_glIndexub = (PFNGLINDEXUBPROC)load("glIndexub"); + glad_glIndexubv = (PFNGLINDEXUBVPROC)load("glIndexubv"); + glad_glPopClientAttrib = (PFNGLPOPCLIENTATTRIBPROC)load("glPopClientAttrib"); + glad_glPushClientAttrib = (PFNGLPUSHCLIENTATTRIBPROC)load("glPushClientAttrib"); +} +static void load_GL_VERSION_1_2(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_2) return; + glad_glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)load("glDrawRangeElements"); + glad_glTexImage3D = (PFNGLTEXIMAGE3DPROC)load("glTexImage3D"); + glad_glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)load("glTexSubImage3D"); + glad_glCopyTexSubImage3D = (PFNGLCOPYTEXSUBIMAGE3DPROC)load("glCopyTexSubImage3D"); +} +static void load_GL_VERSION_1_3(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_3) return; + glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load("glActiveTexture"); + glad_glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC)load("glSampleCoverage"); + glad_glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)load("glCompressedTexImage3D"); + glad_glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)load("glCompressedTexImage2D"); + glad_glCompressedTexImage1D = (PFNGLCOMPRESSEDTEXIMAGE1DPROC)load("glCompressedTexImage1D"); + glad_glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)load("glCompressedTexSubImage3D"); + glad_glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)load("glCompressedTexSubImage2D"); + glad_glCompressedTexSubImage1D = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)load("glCompressedTexSubImage1D"); + glad_glGetCompressedTexImage = (PFNGLGETCOMPRESSEDTEXIMAGEPROC)load("glGetCompressedTexImage"); + glad_glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)load("glClientActiveTexture"); + glad_glMultiTexCoord1d = (PFNGLMULTITEXCOORD1DPROC)load("glMultiTexCoord1d"); + glad_glMultiTexCoord1dv = (PFNGLMULTITEXCOORD1DVPROC)load("glMultiTexCoord1dv"); + glad_glMultiTexCoord1f = (PFNGLMULTITEXCOORD1FPROC)load("glMultiTexCoord1f"); + glad_glMultiTexCoord1fv = (PFNGLMULTITEXCOORD1FVPROC)load("glMultiTexCoord1fv"); + glad_glMultiTexCoord1i = (PFNGLMULTITEXCOORD1IPROC)load("glMultiTexCoord1i"); + glad_glMultiTexCoord1iv = (PFNGLMULTITEXCOORD1IVPROC)load("glMultiTexCoord1iv"); + glad_glMultiTexCoord1s = (PFNGLMULTITEXCOORD1SPROC)load("glMultiTexCoord1s"); + glad_glMultiTexCoord1sv = (PFNGLMULTITEXCOORD1SVPROC)load("glMultiTexCoord1sv"); + glad_glMultiTexCoord2d = (PFNGLMULTITEXCOORD2DPROC)load("glMultiTexCoord2d"); + glad_glMultiTexCoord2dv = (PFNGLMULTITEXCOORD2DVPROC)load("glMultiTexCoord2dv"); + glad_glMultiTexCoord2f = (PFNGLMULTITEXCOORD2FPROC)load("glMultiTexCoord2f"); + glad_glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVPROC)load("glMultiTexCoord2fv"); + glad_glMultiTexCoord2i = (PFNGLMULTITEXCOORD2IPROC)load("glMultiTexCoord2i"); + glad_glMultiTexCoord2iv = (PFNGLMULTITEXCOORD2IVPROC)load("glMultiTexCoord2iv"); + glad_glMultiTexCoord2s = (PFNGLMULTITEXCOORD2SPROC)load("glMultiTexCoord2s"); + glad_glMultiTexCoord2sv = (PFNGLMULTITEXCOORD2SVPROC)load("glMultiTexCoord2sv"); + glad_glMultiTexCoord3d = (PFNGLMULTITEXCOORD3DPROC)load("glMultiTexCoord3d"); + glad_glMultiTexCoord3dv = (PFNGLMULTITEXCOORD3DVPROC)load("glMultiTexCoord3dv"); + glad_glMultiTexCoord3f = (PFNGLMULTITEXCOORD3FPROC)load("glMultiTexCoord3f"); + glad_glMultiTexCoord3fv = (PFNGLMULTITEXCOORD3FVPROC)load("glMultiTexCoord3fv"); + glad_glMultiTexCoord3i = (PFNGLMULTITEXCOORD3IPROC)load("glMultiTexCoord3i"); + glad_glMultiTexCoord3iv = (PFNGLMULTITEXCOORD3IVPROC)load("glMultiTexCoord3iv"); + glad_glMultiTexCoord3s = (PFNGLMULTITEXCOORD3SPROC)load("glMultiTexCoord3s"); + glad_glMultiTexCoord3sv = (PFNGLMULTITEXCOORD3SVPROC)load("glMultiTexCoord3sv"); + glad_glMultiTexCoord4d = (PFNGLMULTITEXCOORD4DPROC)load("glMultiTexCoord4d"); + glad_glMultiTexCoord4dv = (PFNGLMULTITEXCOORD4DVPROC)load("glMultiTexCoord4dv"); + glad_glMultiTexCoord4f = (PFNGLMULTITEXCOORD4FPROC)load("glMultiTexCoord4f"); + glad_glMultiTexCoord4fv = (PFNGLMULTITEXCOORD4FVPROC)load("glMultiTexCoord4fv"); + glad_glMultiTexCoord4i = (PFNGLMULTITEXCOORD4IPROC)load("glMultiTexCoord4i"); + glad_glMultiTexCoord4iv = (PFNGLMULTITEXCOORD4IVPROC)load("glMultiTexCoord4iv"); + glad_glMultiTexCoord4s = (PFNGLMULTITEXCOORD4SPROC)load("glMultiTexCoord4s"); + glad_glMultiTexCoord4sv = (PFNGLMULTITEXCOORD4SVPROC)load("glMultiTexCoord4sv"); + glad_glLoadTransposeMatrixf = (PFNGLLOADTRANSPOSEMATRIXFPROC)load("glLoadTransposeMatrixf"); + glad_glLoadTransposeMatrixd = (PFNGLLOADTRANSPOSEMATRIXDPROC)load("glLoadTransposeMatrixd"); + glad_glMultTransposeMatrixf = (PFNGLMULTTRANSPOSEMATRIXFPROC)load("glMultTransposeMatrixf"); + glad_glMultTransposeMatrixd = (PFNGLMULTTRANSPOSEMATRIXDPROC)load("glMultTransposeMatrixd"); +} +static void load_GL_VERSION_1_4(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_4) return; + glad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)load("glBlendFuncSeparate"); + glad_glMultiDrawArrays = (PFNGLMULTIDRAWARRAYSPROC)load("glMultiDrawArrays"); + glad_glMultiDrawElements = (PFNGLMULTIDRAWELEMENTSPROC)load("glMultiDrawElements"); + glad_glPointParameterf = (PFNGLPOINTPARAMETERFPROC)load("glPointParameterf"); + glad_glPointParameterfv = (PFNGLPOINTPARAMETERFVPROC)load("glPointParameterfv"); + glad_glPointParameteri = (PFNGLPOINTPARAMETERIPROC)load("glPointParameteri"); + glad_glPointParameteriv = (PFNGLPOINTPARAMETERIVPROC)load("glPointParameteriv"); + glad_glFogCoordf = (PFNGLFOGCOORDFPROC)load("glFogCoordf"); + glad_glFogCoordfv = (PFNGLFOGCOORDFVPROC)load("glFogCoordfv"); + glad_glFogCoordd = (PFNGLFOGCOORDDPROC)load("glFogCoordd"); + glad_glFogCoorddv = (PFNGLFOGCOORDDVPROC)load("glFogCoorddv"); + glad_glFogCoordPointer = (PFNGLFOGCOORDPOINTERPROC)load("glFogCoordPointer"); + glad_glSecondaryColor3b = (PFNGLSECONDARYCOLOR3BPROC)load("glSecondaryColor3b"); + glad_glSecondaryColor3bv = (PFNGLSECONDARYCOLOR3BVPROC)load("glSecondaryColor3bv"); + glad_glSecondaryColor3d = (PFNGLSECONDARYCOLOR3DPROC)load("glSecondaryColor3d"); + glad_glSecondaryColor3dv = (PFNGLSECONDARYCOLOR3DVPROC)load("glSecondaryColor3dv"); + glad_glSecondaryColor3f = (PFNGLSECONDARYCOLOR3FPROC)load("glSecondaryColor3f"); + glad_glSecondaryColor3fv = (PFNGLSECONDARYCOLOR3FVPROC)load("glSecondaryColor3fv"); + glad_glSecondaryColor3i = (PFNGLSECONDARYCOLOR3IPROC)load("glSecondaryColor3i"); + glad_glSecondaryColor3iv = (PFNGLSECONDARYCOLOR3IVPROC)load("glSecondaryColor3iv"); + glad_glSecondaryColor3s = (PFNGLSECONDARYCOLOR3SPROC)load("glSecondaryColor3s"); + glad_glSecondaryColor3sv = (PFNGLSECONDARYCOLOR3SVPROC)load("glSecondaryColor3sv"); + glad_glSecondaryColor3ub = (PFNGLSECONDARYCOLOR3UBPROC)load("glSecondaryColor3ub"); + glad_glSecondaryColor3ubv = (PFNGLSECONDARYCOLOR3UBVPROC)load("glSecondaryColor3ubv"); + glad_glSecondaryColor3ui = (PFNGLSECONDARYCOLOR3UIPROC)load("glSecondaryColor3ui"); + glad_glSecondaryColor3uiv = (PFNGLSECONDARYCOLOR3UIVPROC)load("glSecondaryColor3uiv"); + glad_glSecondaryColor3us = (PFNGLSECONDARYCOLOR3USPROC)load("glSecondaryColor3us"); + glad_glSecondaryColor3usv = (PFNGLSECONDARYCOLOR3USVPROC)load("glSecondaryColor3usv"); + glad_glSecondaryColorPointer = (PFNGLSECONDARYCOLORPOINTERPROC)load("glSecondaryColorPointer"); + glad_glWindowPos2d = (PFNGLWINDOWPOS2DPROC)load("glWindowPos2d"); + glad_glWindowPos2dv = (PFNGLWINDOWPOS2DVPROC)load("glWindowPos2dv"); + glad_glWindowPos2f = (PFNGLWINDOWPOS2FPROC)load("glWindowPos2f"); + glad_glWindowPos2fv = (PFNGLWINDOWPOS2FVPROC)load("glWindowPos2fv"); + glad_glWindowPos2i = (PFNGLWINDOWPOS2IPROC)load("glWindowPos2i"); + glad_glWindowPos2iv = (PFNGLWINDOWPOS2IVPROC)load("glWindowPos2iv"); + glad_glWindowPos2s = (PFNGLWINDOWPOS2SPROC)load("glWindowPos2s"); + glad_glWindowPos2sv = (PFNGLWINDOWPOS2SVPROC)load("glWindowPos2sv"); + glad_glWindowPos3d = (PFNGLWINDOWPOS3DPROC)load("glWindowPos3d"); + glad_glWindowPos3dv = (PFNGLWINDOWPOS3DVPROC)load("glWindowPos3dv"); + glad_glWindowPos3f = (PFNGLWINDOWPOS3FPROC)load("glWindowPos3f"); + glad_glWindowPos3fv = (PFNGLWINDOWPOS3FVPROC)load("glWindowPos3fv"); + glad_glWindowPos3i = (PFNGLWINDOWPOS3IPROC)load("glWindowPos3i"); + glad_glWindowPos3iv = (PFNGLWINDOWPOS3IVPROC)load("glWindowPos3iv"); + glad_glWindowPos3s = (PFNGLWINDOWPOS3SPROC)load("glWindowPos3s"); + glad_glWindowPos3sv = (PFNGLWINDOWPOS3SVPROC)load("glWindowPos3sv"); + glad_glBlendColor = (PFNGLBLENDCOLORPROC)load("glBlendColor"); + glad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load("glBlendEquation"); +} +static void load_GL_VERSION_1_5(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_5) return; + glad_glGenQueries = (PFNGLGENQUERIESPROC)load("glGenQueries"); + glad_glDeleteQueries = (PFNGLDELETEQUERIESPROC)load("glDeleteQueries"); + glad_glIsQuery = (PFNGLISQUERYPROC)load("glIsQuery"); + glad_glBeginQuery = (PFNGLBEGINQUERYPROC)load("glBeginQuery"); + glad_glEndQuery = (PFNGLENDQUERYPROC)load("glEndQuery"); + glad_glGetQueryiv = (PFNGLGETQUERYIVPROC)load("glGetQueryiv"); + glad_glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC)load("glGetQueryObjectiv"); + glad_glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)load("glGetQueryObjectuiv"); + glad_glBindBuffer = (PFNGLBINDBUFFERPROC)load("glBindBuffer"); + glad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load("glDeleteBuffers"); + glad_glGenBuffers = (PFNGLGENBUFFERSPROC)load("glGenBuffers"); + glad_glIsBuffer = (PFNGLISBUFFERPROC)load("glIsBuffer"); + glad_glBufferData = (PFNGLBUFFERDATAPROC)load("glBufferData"); + glad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load("glBufferSubData"); + glad_glGetBufferSubData = (PFNGLGETBUFFERSUBDATAPROC)load("glGetBufferSubData"); + glad_glMapBuffer = (PFNGLMAPBUFFERPROC)load("glMapBuffer"); + glad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)load("glUnmapBuffer"); + glad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)load("glGetBufferParameteriv"); + glad_glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC)load("glGetBufferPointerv"); +} +static void load_GL_VERSION_2_0(GLADloadproc load) { + if(!GLAD_GL_VERSION_2_0) return; + glad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)load("glBlendEquationSeparate"); + glad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC)load("glDrawBuffers"); + glad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)load("glStencilOpSeparate"); + glad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)load("glStencilFuncSeparate"); + glad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)load("glStencilMaskSeparate"); + glad_glAttachShader = (PFNGLATTACHSHADERPROC)load("glAttachShader"); + glad_glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)load("glBindAttribLocation"); + glad_glCompileShader = (PFNGLCOMPILESHADERPROC)load("glCompileShader"); + glad_glCreateProgram = (PFNGLCREATEPROGRAMPROC)load("glCreateProgram"); + glad_glCreateShader = (PFNGLCREATESHADERPROC)load("glCreateShader"); + glad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)load("glDeleteProgram"); + glad_glDeleteShader = (PFNGLDELETESHADERPROC)load("glDeleteShader"); + glad_glDetachShader = (PFNGLDETACHSHADERPROC)load("glDetachShader"); + glad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load("glDisableVertexAttribArray"); + glad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load("glEnableVertexAttribArray"); + glad_glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC)load("glGetActiveAttrib"); + glad_glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)load("glGetActiveUniform"); + glad_glGetAttachedShaders = (PFNGLGETATTACHEDSHADERSPROC)load("glGetAttachedShaders"); + glad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)load("glGetAttribLocation"); + glad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)load("glGetProgramiv"); + glad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)load("glGetProgramInfoLog"); + glad_glGetShaderiv = (PFNGLGETSHADERIVPROC)load("glGetShaderiv"); + glad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)load("glGetShaderInfoLog"); + glad_glGetShaderSource = (PFNGLGETSHADERSOURCEPROC)load("glGetShaderSource"); + glad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)load("glGetUniformLocation"); + glad_glGetUniformfv = (PFNGLGETUNIFORMFVPROC)load("glGetUniformfv"); + glad_glGetUniformiv = (PFNGLGETUNIFORMIVPROC)load("glGetUniformiv"); + glad_glGetVertexAttribdv = (PFNGLGETVERTEXATTRIBDVPROC)load("glGetVertexAttribdv"); + glad_glGetVertexAttribfv = (PFNGLGETVERTEXATTRIBFVPROC)load("glGetVertexAttribfv"); + glad_glGetVertexAttribiv = (PFNGLGETVERTEXATTRIBIVPROC)load("glGetVertexAttribiv"); + glad_glGetVertexAttribPointerv = (PFNGLGETVERTEXATTRIBPOINTERVPROC)load("glGetVertexAttribPointerv"); + glad_glIsProgram = (PFNGLISPROGRAMPROC)load("glIsProgram"); + glad_glIsShader = (PFNGLISSHADERPROC)load("glIsShader"); + glad_glLinkProgram = (PFNGLLINKPROGRAMPROC)load("glLinkProgram"); + glad_glShaderSource = (PFNGLSHADERSOURCEPROC)load("glShaderSource"); + glad_glUseProgram = (PFNGLUSEPROGRAMPROC)load("glUseProgram"); + glad_glUniform1f = (PFNGLUNIFORM1FPROC)load("glUniform1f"); + glad_glUniform2f = (PFNGLUNIFORM2FPROC)load("glUniform2f"); + glad_glUniform3f = (PFNGLUNIFORM3FPROC)load("glUniform3f"); + glad_glUniform4f = (PFNGLUNIFORM4FPROC)load("glUniform4f"); + glad_glUniform1i = (PFNGLUNIFORM1IPROC)load("glUniform1i"); + glad_glUniform2i = (PFNGLUNIFORM2IPROC)load("glUniform2i"); + glad_glUniform3i = (PFNGLUNIFORM3IPROC)load("glUniform3i"); + glad_glUniform4i = (PFNGLUNIFORM4IPROC)load("glUniform4i"); + glad_glUniform1fv = (PFNGLUNIFORM1FVPROC)load("glUniform1fv"); + glad_glUniform2fv = (PFNGLUNIFORM2FVPROC)load("glUniform2fv"); + glad_glUniform3fv = (PFNGLUNIFORM3FVPROC)load("glUniform3fv"); + glad_glUniform4fv = (PFNGLUNIFORM4FVPROC)load("glUniform4fv"); + glad_glUniform1iv = (PFNGLUNIFORM1IVPROC)load("glUniform1iv"); + glad_glUniform2iv = (PFNGLUNIFORM2IVPROC)load("glUniform2iv"); + glad_glUniform3iv = (PFNGLUNIFORM3IVPROC)load("glUniform3iv"); + glad_glUniform4iv = (PFNGLUNIFORM4IVPROC)load("glUniform4iv"); + glad_glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)load("glUniformMatrix2fv"); + glad_glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)load("glUniformMatrix3fv"); + glad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)load("glUniformMatrix4fv"); + glad_glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)load("glValidateProgram"); + glad_glVertexAttrib1d = (PFNGLVERTEXATTRIB1DPROC)load("glVertexAttrib1d"); + glad_glVertexAttrib1dv = (PFNGLVERTEXATTRIB1DVPROC)load("glVertexAttrib1dv"); + glad_glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)load("glVertexAttrib1f"); + glad_glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)load("glVertexAttrib1fv"); + glad_glVertexAttrib1s = (PFNGLVERTEXATTRIB1SPROC)load("glVertexAttrib1s"); + glad_glVertexAttrib1sv = (PFNGLVERTEXATTRIB1SVPROC)load("glVertexAttrib1sv"); + glad_glVertexAttrib2d = (PFNGLVERTEXATTRIB2DPROC)load("glVertexAttrib2d"); + glad_glVertexAttrib2dv = (PFNGLVERTEXATTRIB2DVPROC)load("glVertexAttrib2dv"); + glad_glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC)load("glVertexAttrib2f"); + glad_glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)load("glVertexAttrib2fv"); + glad_glVertexAttrib2s = (PFNGLVERTEXATTRIB2SPROC)load("glVertexAttrib2s"); + glad_glVertexAttrib2sv = (PFNGLVERTEXATTRIB2SVPROC)load("glVertexAttrib2sv"); + glad_glVertexAttrib3d = (PFNGLVERTEXATTRIB3DPROC)load("glVertexAttrib3d"); + glad_glVertexAttrib3dv = (PFNGLVERTEXATTRIB3DVPROC)load("glVertexAttrib3dv"); + glad_glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC)load("glVertexAttrib3f"); + glad_glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)load("glVertexAttrib3fv"); + glad_glVertexAttrib3s = (PFNGLVERTEXATTRIB3SPROC)load("glVertexAttrib3s"); + glad_glVertexAttrib3sv = (PFNGLVERTEXATTRIB3SVPROC)load("glVertexAttrib3sv"); + glad_glVertexAttrib4Nbv = (PFNGLVERTEXATTRIB4NBVPROC)load("glVertexAttrib4Nbv"); + glad_glVertexAttrib4Niv = (PFNGLVERTEXATTRIB4NIVPROC)load("glVertexAttrib4Niv"); + glad_glVertexAttrib4Nsv = (PFNGLVERTEXATTRIB4NSVPROC)load("glVertexAttrib4Nsv"); + glad_glVertexAttrib4Nub = (PFNGLVERTEXATTRIB4NUBPROC)load("glVertexAttrib4Nub"); + glad_glVertexAttrib4Nubv = (PFNGLVERTEXATTRIB4NUBVPROC)load("glVertexAttrib4Nubv"); + glad_glVertexAttrib4Nuiv = (PFNGLVERTEXATTRIB4NUIVPROC)load("glVertexAttrib4Nuiv"); + glad_glVertexAttrib4Nusv = (PFNGLVERTEXATTRIB4NUSVPROC)load("glVertexAttrib4Nusv"); + glad_glVertexAttrib4bv = (PFNGLVERTEXATTRIB4BVPROC)load("glVertexAttrib4bv"); + glad_glVertexAttrib4d = (PFNGLVERTEXATTRIB4DPROC)load("glVertexAttrib4d"); + glad_glVertexAttrib4dv = (PFNGLVERTEXATTRIB4DVPROC)load("glVertexAttrib4dv"); + glad_glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC)load("glVertexAttrib4f"); + glad_glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)load("glVertexAttrib4fv"); + glad_glVertexAttrib4iv = (PFNGLVERTEXATTRIB4IVPROC)load("glVertexAttrib4iv"); + glad_glVertexAttrib4s = (PFNGLVERTEXATTRIB4SPROC)load("glVertexAttrib4s"); + glad_glVertexAttrib4sv = (PFNGLVERTEXATTRIB4SVPROC)load("glVertexAttrib4sv"); + glad_glVertexAttrib4ubv = (PFNGLVERTEXATTRIB4UBVPROC)load("glVertexAttrib4ubv"); + glad_glVertexAttrib4uiv = (PFNGLVERTEXATTRIB4UIVPROC)load("glVertexAttrib4uiv"); + glad_glVertexAttrib4usv = (PFNGLVERTEXATTRIB4USVPROC)load("glVertexAttrib4usv"); + glad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load("glVertexAttribPointer"); +} +static void load_GL_VERSION_2_1(GLADloadproc load) { + if(!GLAD_GL_VERSION_2_1) return; + glad_glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)load("glUniformMatrix2x3fv"); + glad_glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)load("glUniformMatrix3x2fv"); + glad_glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)load("glUniformMatrix2x4fv"); + glad_glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)load("glUniformMatrix4x2fv"); + glad_glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)load("glUniformMatrix3x4fv"); + glad_glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)load("glUniformMatrix4x3fv"); +} +static int find_extensionsGL(void) { + if (!get_exts()) return 0; + (void)&has_ext; + free_exts(); + return 1; +} + +static void find_coreGL(void) { + + /* Thank you @elmindreda + * https://github.com/elmindreda/greg/blob/master/templates/greg.c.in#L176 + * https://github.com/glfw/glfw/blob/master/src/context.c#L36 + */ + int i, major, minor; + + const char* version; + const char* prefixes[] = { + "OpenGL ES-CM ", + "OpenGL ES-CL ", + "OpenGL ES ", + NULL + }; + + version = (const char*) glGetString(GL_VERSION); + if (!version) return; + + for (i = 0; prefixes[i]; i++) { + const size_t length = strlen(prefixes[i]); + if (strncmp(version, prefixes[i], length) == 0) { + version += length; + break; + } + } + +/* PR #18 */ +#ifdef _MSC_VER + sscanf_s(version, "%d.%d", &major, &minor); +#else + sscanf(version, "%d.%d", &major, &minor); +#endif + + GLVersion.major = major; GLVersion.minor = minor; + max_loaded_major = major; max_loaded_minor = minor; + GLAD_GL_VERSION_1_0 = (major == 1 && minor >= 0) || major > 1; + GLAD_GL_VERSION_1_1 = (major == 1 && minor >= 1) || major > 1; + GLAD_GL_VERSION_1_2 = (major == 1 && minor >= 2) || major > 1; + GLAD_GL_VERSION_1_3 = (major == 1 && minor >= 3) || major > 1; + GLAD_GL_VERSION_1_4 = (major == 1 && minor >= 4) || major > 1; + GLAD_GL_VERSION_1_5 = (major == 1 && minor >= 5) || major > 1; + GLAD_GL_VERSION_2_0 = (major == 2 && minor >= 0) || major > 2; + GLAD_GL_VERSION_2_1 = (major == 2 && minor >= 1) || major > 2; + if (GLVersion.major > 2 || (GLVersion.major >= 2 && GLVersion.minor >= 1)) { + max_loaded_major = 2; + max_loaded_minor = 1; + } +} + +int gladLoadGLLoader(GLADloadproc load) { + GLVersion.major = 0; GLVersion.minor = 0; + glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + if(glGetString == NULL) return 0; + if(glGetString(GL_VERSION) == NULL) return 0; + find_coreGL(); + load_GL_VERSION_1_0(load); + load_GL_VERSION_1_1(load); + load_GL_VERSION_1_2(load); + load_GL_VERSION_1_3(load); + load_GL_VERSION_1_4(load); + load_GL_VERSION_1_5(load); + load_GL_VERSION_2_0(load); + load_GL_VERSION_2_1(load); + + if (!find_extensionsGL()) return 0; + return GLVersion.major != 0 || GLVersion.minor != 0; +} + diff --git a/shf/ffi/glad.h b/shf/ffi/glad.h new file mode 100644 index 0000000..4fa9465 --- /dev/null +++ b/shf/ffi/glad.h @@ -0,0 +1,2749 @@ +/* + + OpenGL loader generated by glad 0.1.27 on Tue Sep 4 23:27:29 2018. + + Language/Generator: C/C++ + Specification: gl + APIs: gl=2.1 + Profile: compatibility + Extensions: + + Loader: True + Local files: False + Omit khrplatform: False + + Commandline: + --profile="compatibility" --api="gl=2.1" --generator="c" --spec="gl" --extensions="" + Online: + http://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D2.1 +*/ + + +#ifndef __glad_h_ +#define __glad_h_ + +#ifdef __gl_h_ +#error OpenGL header already included, remove this include, glad already provides it +#endif +#define __gl_h_ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef NOMINMAX +#define NOMINMAX 1 +#endif +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif + +#ifndef GLAPIENTRY +#define GLAPIENTRY APIENTRY +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct gladGLversionStruct { + int major; + int minor; +}; + +typedef void* (* GLADloadproc)(const char *name); + +#ifndef GLAPI +# if defined(GLAD_GLAPI_EXPORT) +# if defined(_WIN32) || defined(__CYGWIN__) +# if defined(GLAD_GLAPI_EXPORT_BUILD) +# if defined(__GNUC__) +# define GLAPI __attribute__ ((dllexport)) extern +# else +# define GLAPI __declspec(dllexport) extern +# endif +# else +# if defined(__GNUC__) +# define GLAPI __attribute__ ((dllimport)) extern +# else +# define GLAPI __declspec(dllimport) extern +# endif +# endif +# elif defined(__GNUC__) && defined(GLAD_GLAPI_EXPORT_BUILD) +# define GLAPI __attribute__ ((visibility ("default"))) extern +# else +# define GLAPI extern +# endif +# else +# define GLAPI extern +# endif +#endif + +GLAPI struct gladGLversionStruct GLVersion; + +GLAPI int gladLoadGL(void); + +GLAPI int gladLoadGLLoader(GLADloadproc); + +#include +#include "khrplatform.h" +#ifndef GLEXT_64_TYPES_DEFINED +/* This code block is duplicated in glxext.h, so must be protected */ +#define GLEXT_64_TYPES_DEFINED +/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ +/* (as used in the GL_EXT_timer_query extension). */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#elif defined(__sun__) || defined(__digital__) +#include +#if defined(__STDC__) +#if defined(__arch64__) || defined(_LP64) +typedef long int int64_t; +typedef unsigned long int uint64_t; +#else +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#endif /* __arch64__ */ +#endif /* __STDC__ */ +#elif defined( __VMS ) || defined(__sgi) +#include +#elif defined(__SCO__) || defined(__USLC__) +#include +#elif defined(__UNIXOS2__) || defined(__SOL64__) +typedef long int int32_t; +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#elif defined(_WIN32) && defined(__GNUC__) +#include +#elif defined(_WIN32) +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +/* Fallback if nothing above works */ +#include +#endif +#endif +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLclampx; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef int GLsizei; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void *GLeglClientBufferEXT; +typedef void *GLeglImageOES; +typedef char GLchar; +typedef char GLcharARB; +#ifdef __APPLE__ +typedef void *GLhandleARB; +#else +typedef unsigned int GLhandleARB; +#endif +typedef unsigned short GLhalfARB; +typedef unsigned short GLhalf; +typedef GLint GLfixed; +typedef khronos_intptr_t GLintptr; +typedef khronos_ssize_t GLsizeiptr; +typedef int64_t GLint64; +typedef uint64_t GLuint64; +#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) +typedef long GLintptrARB; +#else +typedef ptrdiff_t GLintptrARB; +#endif +#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) +typedef long GLsizeiptrARB; +#else +typedef ptrdiff_t GLsizeiptrARB; +#endif +typedef int64_t GLint64EXT; +typedef uint64_t GLuint64EXT; +typedef struct __GLsync *GLsync; +struct _cl_context; +struct _cl_event; +typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +typedef void (APIENTRY *GLDEBUGPROCKHR)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam); +typedef unsigned short GLhalfNV; +typedef GLintptr GLvdpauSurfaceNV; +typedef void (APIENTRY *GLVULKANPROCNV)(void); +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_VIEWPORT 0x0BA2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F +#define GL_TEXTURE 0x1702 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_REPEAT 0x2901 +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0xFFFFFFFF +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_LOGIC_OP 0x0BF1 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_TEXTURE_COMPONENTS 0x1003 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_COLOR_INDEX 0x1900 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_BITMAP 0x1A00 +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_ENV 0x2300 +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_CLAMP 0x2900 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_DOUBLE 0x140A +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xFFFFFFFF +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_RESCALE_NORMAL 0x803A +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#define GL_BLEND_COLOR 0x8005 +#define GL_BLEND_EQUATION 0x8009 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_FUNC_SUBTRACT 0x800A +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_SRC1_ALPHA 0x8589 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_FOG_COORD_SRC 0x8450 +#define GL_FOG_COORD 0x8451 +#define GL_CURRENT_FOG_COORD 0x8453 +#define GL_FOG_COORD_ARRAY_TYPE 0x8454 +#define GL_FOG_COORD_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORD_ARRAY_POINTER 0x8456 +#define GL_FOG_COORD_ARRAY 0x8457 +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D +#define GL_SRC0_RGB 0x8580 +#define GL_SRC1_RGB 0x8581 +#define GL_SRC2_RGB 0x8582 +#define GL_SRC0_ALPHA 0x8588 +#define GL_SRC2_ALPHA 0x858A +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_COMPRESSED_SLUMINANCE 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B +#ifndef GL_VERSION_1_0 +#define GL_VERSION_1_0 1 +GLAPI int GLAD_GL_VERSION_1_0; +typedef void (APIENTRYP PFNGLCULLFACEPROC)(GLenum mode); +GLAPI PFNGLCULLFACEPROC glad_glCullFace; +#define glCullFace glad_glCullFace +typedef void (APIENTRYP PFNGLFRONTFACEPROC)(GLenum mode); +GLAPI PFNGLFRONTFACEPROC glad_glFrontFace; +#define glFrontFace glad_glFrontFace +typedef void (APIENTRYP PFNGLHINTPROC)(GLenum target, GLenum mode); +GLAPI PFNGLHINTPROC glad_glHint; +#define glHint glad_glHint +typedef void (APIENTRYP PFNGLLINEWIDTHPROC)(GLfloat width); +GLAPI PFNGLLINEWIDTHPROC glad_glLineWidth; +#define glLineWidth glad_glLineWidth +typedef void (APIENTRYP PFNGLPOINTSIZEPROC)(GLfloat size); +GLAPI PFNGLPOINTSIZEPROC glad_glPointSize; +#define glPointSize glad_glPointSize +typedef void (APIENTRYP PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode); +GLAPI PFNGLPOLYGONMODEPROC glad_glPolygonMode; +#define glPolygonMode glad_glPolygonMode +typedef void (APIENTRYP PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLSCISSORPROC glad_glScissor; +#define glScissor glad_glScissor +typedef void (APIENTRYP PFNGLTEXPARAMETERFPROC)(GLenum target, GLenum pname, GLfloat param); +GLAPI PFNGLTEXPARAMETERFPROC glad_glTexParameterf; +#define glTexParameterf glad_glTexParameterf +typedef void (APIENTRYP PFNGLTEXPARAMETERFVPROC)(GLenum target, GLenum pname, const GLfloat *params); +GLAPI PFNGLTEXPARAMETERFVPROC glad_glTexParameterfv; +#define glTexParameterfv glad_glTexParameterfv +typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, GLint param); +GLAPI PFNGLTEXPARAMETERIPROC glad_glTexParameteri; +#define glTexParameteri glad_glTexParameteri +typedef void (APIENTRYP PFNGLTEXPARAMETERIVPROC)(GLenum target, GLenum pname, const GLint *params); +GLAPI PFNGLTEXPARAMETERIVPROC glad_glTexParameteriv; +#define glTexParameteriv glad_glTexParameteriv +typedef void (APIENTRYP PFNGLTEXIMAGE1DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXIMAGE1DPROC glad_glTexImage1D; +#define glTexImage1D glad_glTexImage1D +typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXIMAGE2DPROC glad_glTexImage2D; +#define glTexImage2D glad_glTexImage2D +typedef void (APIENTRYP PFNGLDRAWBUFFERPROC)(GLenum buf); +GLAPI PFNGLDRAWBUFFERPROC glad_glDrawBuffer; +#define glDrawBuffer glad_glDrawBuffer +typedef void (APIENTRYP PFNGLCLEARPROC)(GLbitfield mask); +GLAPI PFNGLCLEARPROC glad_glClear; +#define glClear glad_glClear +typedef void (APIENTRYP PFNGLCLEARCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI PFNGLCLEARCOLORPROC glad_glClearColor; +#define glClearColor glad_glClearColor +typedef void (APIENTRYP PFNGLCLEARSTENCILPROC)(GLint s); +GLAPI PFNGLCLEARSTENCILPROC glad_glClearStencil; +#define glClearStencil glad_glClearStencil +typedef void (APIENTRYP PFNGLCLEARDEPTHPROC)(GLdouble depth); +GLAPI PFNGLCLEARDEPTHPROC glad_glClearDepth; +#define glClearDepth glad_glClearDepth +typedef void (APIENTRYP PFNGLSTENCILMASKPROC)(GLuint mask); +GLAPI PFNGLSTENCILMASKPROC glad_glStencilMask; +#define glStencilMask glad_glStencilMask +typedef void (APIENTRYP PFNGLCOLORMASKPROC)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +GLAPI PFNGLCOLORMASKPROC glad_glColorMask; +#define glColorMask glad_glColorMask +typedef void (APIENTRYP PFNGLDEPTHMASKPROC)(GLboolean flag); +GLAPI PFNGLDEPTHMASKPROC glad_glDepthMask; +#define glDepthMask glad_glDepthMask +typedef void (APIENTRYP PFNGLDISABLEPROC)(GLenum cap); +GLAPI PFNGLDISABLEPROC glad_glDisable; +#define glDisable glad_glDisable +typedef void (APIENTRYP PFNGLENABLEPROC)(GLenum cap); +GLAPI PFNGLENABLEPROC glad_glEnable; +#define glEnable glad_glEnable +typedef void (APIENTRYP PFNGLFINISHPROC)(void); +GLAPI PFNGLFINISHPROC glad_glFinish; +#define glFinish glad_glFinish +typedef void (APIENTRYP PFNGLFLUSHPROC)(void); +GLAPI PFNGLFLUSHPROC glad_glFlush; +#define glFlush glad_glFlush +typedef void (APIENTRYP PFNGLBLENDFUNCPROC)(GLenum sfactor, GLenum dfactor); +GLAPI PFNGLBLENDFUNCPROC glad_glBlendFunc; +#define glBlendFunc glad_glBlendFunc +typedef void (APIENTRYP PFNGLLOGICOPPROC)(GLenum opcode); +GLAPI PFNGLLOGICOPPROC glad_glLogicOp; +#define glLogicOp glad_glLogicOp +typedef void (APIENTRYP PFNGLSTENCILFUNCPROC)(GLenum func, GLint ref, GLuint mask); +GLAPI PFNGLSTENCILFUNCPROC glad_glStencilFunc; +#define glStencilFunc glad_glStencilFunc +typedef void (APIENTRYP PFNGLSTENCILOPPROC)(GLenum fail, GLenum zfail, GLenum zpass); +GLAPI PFNGLSTENCILOPPROC glad_glStencilOp; +#define glStencilOp glad_glStencilOp +typedef void (APIENTRYP PFNGLDEPTHFUNCPROC)(GLenum func); +GLAPI PFNGLDEPTHFUNCPROC glad_glDepthFunc; +#define glDepthFunc glad_glDepthFunc +typedef void (APIENTRYP PFNGLPIXELSTOREFPROC)(GLenum pname, GLfloat param); +GLAPI PFNGLPIXELSTOREFPROC glad_glPixelStoref; +#define glPixelStoref glad_glPixelStoref +typedef void (APIENTRYP PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param); +GLAPI PFNGLPIXELSTOREIPROC glad_glPixelStorei; +#define glPixelStorei glad_glPixelStorei +typedef void (APIENTRYP PFNGLREADBUFFERPROC)(GLenum src); +GLAPI PFNGLREADBUFFERPROC glad_glReadBuffer; +#define glReadBuffer glad_glReadBuffer +typedef void (APIENTRYP PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +GLAPI PFNGLREADPIXELSPROC glad_glReadPixels; +#define glReadPixels glad_glReadPixels +typedef void (APIENTRYP PFNGLGETBOOLEANVPROC)(GLenum pname, GLboolean *data); +GLAPI PFNGLGETBOOLEANVPROC glad_glGetBooleanv; +#define glGetBooleanv glad_glGetBooleanv +typedef void (APIENTRYP PFNGLGETDOUBLEVPROC)(GLenum pname, GLdouble *data); +GLAPI PFNGLGETDOUBLEVPROC glad_glGetDoublev; +#define glGetDoublev glad_glGetDoublev +typedef GLenum (APIENTRYP PFNGLGETERRORPROC)(void); +GLAPI PFNGLGETERRORPROC glad_glGetError; +#define glGetError glad_glGetError +typedef void (APIENTRYP PFNGLGETFLOATVPROC)(GLenum pname, GLfloat *data); +GLAPI PFNGLGETFLOATVPROC glad_glGetFloatv; +#define glGetFloatv glad_glGetFloatv +typedef void (APIENTRYP PFNGLGETINTEGERVPROC)(GLenum pname, GLint *data); +GLAPI PFNGLGETINTEGERVPROC glad_glGetIntegerv; +#define glGetIntegerv glad_glGetIntegerv +typedef const GLubyte * (APIENTRYP PFNGLGETSTRINGPROC)(GLenum name); +GLAPI PFNGLGETSTRINGPROC glad_glGetString; +#define glGetString glad_glGetString +typedef void (APIENTRYP PFNGLGETTEXIMAGEPROC)(GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI PFNGLGETTEXIMAGEPROC glad_glGetTexImage; +#define glGetTexImage glad_glGetTexImage +typedef void (APIENTRYP PFNGLGETTEXPARAMETERFVPROC)(GLenum target, GLenum pname, GLfloat *params); +GLAPI PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv; +#define glGetTexParameterfv glad_glGetTexParameterfv +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv; +#define glGetTexParameteriv glad_glGetTexParameteriv +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERFVPROC)(GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI PFNGLGETTEXLEVELPARAMETERFVPROC glad_glGetTexLevelParameterfv; +#define glGetTexLevelParameterfv glad_glGetTexLevelParameterfv +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERIVPROC)(GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI PFNGLGETTEXLEVELPARAMETERIVPROC glad_glGetTexLevelParameteriv; +#define glGetTexLevelParameteriv glad_glGetTexLevelParameteriv +typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC)(GLenum cap); +GLAPI PFNGLISENABLEDPROC glad_glIsEnabled; +#define glIsEnabled glad_glIsEnabled +typedef void (APIENTRYP PFNGLDEPTHRANGEPROC)(GLdouble n, GLdouble f); +GLAPI PFNGLDEPTHRANGEPROC glad_glDepthRange; +#define glDepthRange glad_glDepthRange +typedef void (APIENTRYP PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLVIEWPORTPROC glad_glViewport; +#define glViewport glad_glViewport +typedef void (APIENTRYP PFNGLNEWLISTPROC)(GLuint list, GLenum mode); +GLAPI PFNGLNEWLISTPROC glad_glNewList; +#define glNewList glad_glNewList +typedef void (APIENTRYP PFNGLENDLISTPROC)(void); +GLAPI PFNGLENDLISTPROC glad_glEndList; +#define glEndList glad_glEndList +typedef void (APIENTRYP PFNGLCALLLISTPROC)(GLuint list); +GLAPI PFNGLCALLLISTPROC glad_glCallList; +#define glCallList glad_glCallList +typedef void (APIENTRYP PFNGLCALLLISTSPROC)(GLsizei n, GLenum type, const void *lists); +GLAPI PFNGLCALLLISTSPROC glad_glCallLists; +#define glCallLists glad_glCallLists +typedef void (APIENTRYP PFNGLDELETELISTSPROC)(GLuint list, GLsizei range); +GLAPI PFNGLDELETELISTSPROC glad_glDeleteLists; +#define glDeleteLists glad_glDeleteLists +typedef GLuint (APIENTRYP PFNGLGENLISTSPROC)(GLsizei range); +GLAPI PFNGLGENLISTSPROC glad_glGenLists; +#define glGenLists glad_glGenLists +typedef void (APIENTRYP PFNGLLISTBASEPROC)(GLuint base); +GLAPI PFNGLLISTBASEPROC glad_glListBase; +#define glListBase glad_glListBase +typedef void (APIENTRYP PFNGLBEGINPROC)(GLenum mode); +GLAPI PFNGLBEGINPROC glad_glBegin; +#define glBegin glad_glBegin +typedef void (APIENTRYP PFNGLBITMAPPROC)(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +GLAPI PFNGLBITMAPPROC glad_glBitmap; +#define glBitmap glad_glBitmap +typedef void (APIENTRYP PFNGLCOLOR3BPROC)(GLbyte red, GLbyte green, GLbyte blue); +GLAPI PFNGLCOLOR3BPROC glad_glColor3b; +#define glColor3b glad_glColor3b +typedef void (APIENTRYP PFNGLCOLOR3BVPROC)(const GLbyte *v); +GLAPI PFNGLCOLOR3BVPROC glad_glColor3bv; +#define glColor3bv glad_glColor3bv +typedef void (APIENTRYP PFNGLCOLOR3DPROC)(GLdouble red, GLdouble green, GLdouble blue); +GLAPI PFNGLCOLOR3DPROC glad_glColor3d; +#define glColor3d glad_glColor3d +typedef void (APIENTRYP PFNGLCOLOR3DVPROC)(const GLdouble *v); +GLAPI PFNGLCOLOR3DVPROC glad_glColor3dv; +#define glColor3dv glad_glColor3dv +typedef void (APIENTRYP PFNGLCOLOR3FPROC)(GLfloat red, GLfloat green, GLfloat blue); +GLAPI PFNGLCOLOR3FPROC glad_glColor3f; +#define glColor3f glad_glColor3f +typedef void (APIENTRYP PFNGLCOLOR3FVPROC)(const GLfloat *v); +GLAPI PFNGLCOLOR3FVPROC glad_glColor3fv; +#define glColor3fv glad_glColor3fv +typedef void (APIENTRYP PFNGLCOLOR3IPROC)(GLint red, GLint green, GLint blue); +GLAPI PFNGLCOLOR3IPROC glad_glColor3i; +#define glColor3i glad_glColor3i +typedef void (APIENTRYP PFNGLCOLOR3IVPROC)(const GLint *v); +GLAPI PFNGLCOLOR3IVPROC glad_glColor3iv; +#define glColor3iv glad_glColor3iv +typedef void (APIENTRYP PFNGLCOLOR3SPROC)(GLshort red, GLshort green, GLshort blue); +GLAPI PFNGLCOLOR3SPROC glad_glColor3s; +#define glColor3s glad_glColor3s +typedef void (APIENTRYP PFNGLCOLOR3SVPROC)(const GLshort *v); +GLAPI PFNGLCOLOR3SVPROC glad_glColor3sv; +#define glColor3sv glad_glColor3sv +typedef void (APIENTRYP PFNGLCOLOR3UBPROC)(GLubyte red, GLubyte green, GLubyte blue); +GLAPI PFNGLCOLOR3UBPROC glad_glColor3ub; +#define glColor3ub glad_glColor3ub +typedef void (APIENTRYP PFNGLCOLOR3UBVPROC)(const GLubyte *v); +GLAPI PFNGLCOLOR3UBVPROC glad_glColor3ubv; +#define glColor3ubv glad_glColor3ubv +typedef void (APIENTRYP PFNGLCOLOR3UIPROC)(GLuint red, GLuint green, GLuint blue); +GLAPI PFNGLCOLOR3UIPROC glad_glColor3ui; +#define glColor3ui glad_glColor3ui +typedef void (APIENTRYP PFNGLCOLOR3UIVPROC)(const GLuint *v); +GLAPI PFNGLCOLOR3UIVPROC glad_glColor3uiv; +#define glColor3uiv glad_glColor3uiv +typedef void (APIENTRYP PFNGLCOLOR3USPROC)(GLushort red, GLushort green, GLushort blue); +GLAPI PFNGLCOLOR3USPROC glad_glColor3us; +#define glColor3us glad_glColor3us +typedef void (APIENTRYP PFNGLCOLOR3USVPROC)(const GLushort *v); +GLAPI PFNGLCOLOR3USVPROC glad_glColor3usv; +#define glColor3usv glad_glColor3usv +typedef void (APIENTRYP PFNGLCOLOR4BPROC)(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +GLAPI PFNGLCOLOR4BPROC glad_glColor4b; +#define glColor4b glad_glColor4b +typedef void (APIENTRYP PFNGLCOLOR4BVPROC)(const GLbyte *v); +GLAPI PFNGLCOLOR4BVPROC glad_glColor4bv; +#define glColor4bv glad_glColor4bv +typedef void (APIENTRYP PFNGLCOLOR4DPROC)(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +GLAPI PFNGLCOLOR4DPROC glad_glColor4d; +#define glColor4d glad_glColor4d +typedef void (APIENTRYP PFNGLCOLOR4DVPROC)(const GLdouble *v); +GLAPI PFNGLCOLOR4DVPROC glad_glColor4dv; +#define glColor4dv glad_glColor4dv +typedef void (APIENTRYP PFNGLCOLOR4FPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI PFNGLCOLOR4FPROC glad_glColor4f; +#define glColor4f glad_glColor4f +typedef void (APIENTRYP PFNGLCOLOR4FVPROC)(const GLfloat *v); +GLAPI PFNGLCOLOR4FVPROC glad_glColor4fv; +#define glColor4fv glad_glColor4fv +typedef void (APIENTRYP PFNGLCOLOR4IPROC)(GLint red, GLint green, GLint blue, GLint alpha); +GLAPI PFNGLCOLOR4IPROC glad_glColor4i; +#define glColor4i glad_glColor4i +typedef void (APIENTRYP PFNGLCOLOR4IVPROC)(const GLint *v); +GLAPI PFNGLCOLOR4IVPROC glad_glColor4iv; +#define glColor4iv glad_glColor4iv +typedef void (APIENTRYP PFNGLCOLOR4SPROC)(GLshort red, GLshort green, GLshort blue, GLshort alpha); +GLAPI PFNGLCOLOR4SPROC glad_glColor4s; +#define glColor4s glad_glColor4s +typedef void (APIENTRYP PFNGLCOLOR4SVPROC)(const GLshort *v); +GLAPI PFNGLCOLOR4SVPROC glad_glColor4sv; +#define glColor4sv glad_glColor4sv +typedef void (APIENTRYP PFNGLCOLOR4UBPROC)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +GLAPI PFNGLCOLOR4UBPROC glad_glColor4ub; +#define glColor4ub glad_glColor4ub +typedef void (APIENTRYP PFNGLCOLOR4UBVPROC)(const GLubyte *v); +GLAPI PFNGLCOLOR4UBVPROC glad_glColor4ubv; +#define glColor4ubv glad_glColor4ubv +typedef void (APIENTRYP PFNGLCOLOR4UIPROC)(GLuint red, GLuint green, GLuint blue, GLuint alpha); +GLAPI PFNGLCOLOR4UIPROC glad_glColor4ui; +#define glColor4ui glad_glColor4ui +typedef void (APIENTRYP PFNGLCOLOR4UIVPROC)(const GLuint *v); +GLAPI PFNGLCOLOR4UIVPROC glad_glColor4uiv; +#define glColor4uiv glad_glColor4uiv +typedef void (APIENTRYP PFNGLCOLOR4USPROC)(GLushort red, GLushort green, GLushort blue, GLushort alpha); +GLAPI PFNGLCOLOR4USPROC glad_glColor4us; +#define glColor4us glad_glColor4us +typedef void (APIENTRYP PFNGLCOLOR4USVPROC)(const GLushort *v); +GLAPI PFNGLCOLOR4USVPROC glad_glColor4usv; +#define glColor4usv glad_glColor4usv +typedef void (APIENTRYP PFNGLEDGEFLAGPROC)(GLboolean flag); +GLAPI PFNGLEDGEFLAGPROC glad_glEdgeFlag; +#define glEdgeFlag glad_glEdgeFlag +typedef void (APIENTRYP PFNGLEDGEFLAGVPROC)(const GLboolean *flag); +GLAPI PFNGLEDGEFLAGVPROC glad_glEdgeFlagv; +#define glEdgeFlagv glad_glEdgeFlagv +typedef void (APIENTRYP PFNGLENDPROC)(void); +GLAPI PFNGLENDPROC glad_glEnd; +#define glEnd glad_glEnd +typedef void (APIENTRYP PFNGLINDEXDPROC)(GLdouble c); +GLAPI PFNGLINDEXDPROC glad_glIndexd; +#define glIndexd glad_glIndexd +typedef void (APIENTRYP PFNGLINDEXDVPROC)(const GLdouble *c); +GLAPI PFNGLINDEXDVPROC glad_glIndexdv; +#define glIndexdv glad_glIndexdv +typedef void (APIENTRYP PFNGLINDEXFPROC)(GLfloat c); +GLAPI PFNGLINDEXFPROC glad_glIndexf; +#define glIndexf glad_glIndexf +typedef void (APIENTRYP PFNGLINDEXFVPROC)(const GLfloat *c); +GLAPI PFNGLINDEXFVPROC glad_glIndexfv; +#define glIndexfv glad_glIndexfv +typedef void (APIENTRYP PFNGLINDEXIPROC)(GLint c); +GLAPI PFNGLINDEXIPROC glad_glIndexi; +#define glIndexi glad_glIndexi +typedef void (APIENTRYP PFNGLINDEXIVPROC)(const GLint *c); +GLAPI PFNGLINDEXIVPROC glad_glIndexiv; +#define glIndexiv glad_glIndexiv +typedef void (APIENTRYP PFNGLINDEXSPROC)(GLshort c); +GLAPI PFNGLINDEXSPROC glad_glIndexs; +#define glIndexs glad_glIndexs +typedef void (APIENTRYP PFNGLINDEXSVPROC)(const GLshort *c); +GLAPI PFNGLINDEXSVPROC glad_glIndexsv; +#define glIndexsv glad_glIndexsv +typedef void (APIENTRYP PFNGLNORMAL3BPROC)(GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI PFNGLNORMAL3BPROC glad_glNormal3b; +#define glNormal3b glad_glNormal3b +typedef void (APIENTRYP PFNGLNORMAL3BVPROC)(const GLbyte *v); +GLAPI PFNGLNORMAL3BVPROC glad_glNormal3bv; +#define glNormal3bv glad_glNormal3bv +typedef void (APIENTRYP PFNGLNORMAL3DPROC)(GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI PFNGLNORMAL3DPROC glad_glNormal3d; +#define glNormal3d glad_glNormal3d +typedef void (APIENTRYP PFNGLNORMAL3DVPROC)(const GLdouble *v); +GLAPI PFNGLNORMAL3DVPROC glad_glNormal3dv; +#define glNormal3dv glad_glNormal3dv +typedef void (APIENTRYP PFNGLNORMAL3FPROC)(GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI PFNGLNORMAL3FPROC glad_glNormal3f; +#define glNormal3f glad_glNormal3f +typedef void (APIENTRYP PFNGLNORMAL3FVPROC)(const GLfloat *v); +GLAPI PFNGLNORMAL3FVPROC glad_glNormal3fv; +#define glNormal3fv glad_glNormal3fv +typedef void (APIENTRYP PFNGLNORMAL3IPROC)(GLint nx, GLint ny, GLint nz); +GLAPI PFNGLNORMAL3IPROC glad_glNormal3i; +#define glNormal3i glad_glNormal3i +typedef void (APIENTRYP PFNGLNORMAL3IVPROC)(const GLint *v); +GLAPI PFNGLNORMAL3IVPROC glad_glNormal3iv; +#define glNormal3iv glad_glNormal3iv +typedef void (APIENTRYP PFNGLNORMAL3SPROC)(GLshort nx, GLshort ny, GLshort nz); +GLAPI PFNGLNORMAL3SPROC glad_glNormal3s; +#define glNormal3s glad_glNormal3s +typedef void (APIENTRYP PFNGLNORMAL3SVPROC)(const GLshort *v); +GLAPI PFNGLNORMAL3SVPROC glad_glNormal3sv; +#define glNormal3sv glad_glNormal3sv +typedef void (APIENTRYP PFNGLRASTERPOS2DPROC)(GLdouble x, GLdouble y); +GLAPI PFNGLRASTERPOS2DPROC glad_glRasterPos2d; +#define glRasterPos2d glad_glRasterPos2d +typedef void (APIENTRYP PFNGLRASTERPOS2DVPROC)(const GLdouble *v); +GLAPI PFNGLRASTERPOS2DVPROC glad_glRasterPos2dv; +#define glRasterPos2dv glad_glRasterPos2dv +typedef void (APIENTRYP PFNGLRASTERPOS2FPROC)(GLfloat x, GLfloat y); +GLAPI PFNGLRASTERPOS2FPROC glad_glRasterPos2f; +#define glRasterPos2f glad_glRasterPos2f +typedef void (APIENTRYP PFNGLRASTERPOS2FVPROC)(const GLfloat *v); +GLAPI PFNGLRASTERPOS2FVPROC glad_glRasterPos2fv; +#define glRasterPos2fv glad_glRasterPos2fv +typedef void (APIENTRYP PFNGLRASTERPOS2IPROC)(GLint x, GLint y); +GLAPI PFNGLRASTERPOS2IPROC glad_glRasterPos2i; +#define glRasterPos2i glad_glRasterPos2i +typedef void (APIENTRYP PFNGLRASTERPOS2IVPROC)(const GLint *v); +GLAPI PFNGLRASTERPOS2IVPROC glad_glRasterPos2iv; +#define glRasterPos2iv glad_glRasterPos2iv +typedef void (APIENTRYP PFNGLRASTERPOS2SPROC)(GLshort x, GLshort y); +GLAPI PFNGLRASTERPOS2SPROC glad_glRasterPos2s; +#define glRasterPos2s glad_glRasterPos2s +typedef void (APIENTRYP PFNGLRASTERPOS2SVPROC)(const GLshort *v); +GLAPI PFNGLRASTERPOS2SVPROC glad_glRasterPos2sv; +#define glRasterPos2sv glad_glRasterPos2sv +typedef void (APIENTRYP PFNGLRASTERPOS3DPROC)(GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLRASTERPOS3DPROC glad_glRasterPos3d; +#define glRasterPos3d glad_glRasterPos3d +typedef void (APIENTRYP PFNGLRASTERPOS3DVPROC)(const GLdouble *v); +GLAPI PFNGLRASTERPOS3DVPROC glad_glRasterPos3dv; +#define glRasterPos3dv glad_glRasterPos3dv +typedef void (APIENTRYP PFNGLRASTERPOS3FPROC)(GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLRASTERPOS3FPROC glad_glRasterPos3f; +#define glRasterPos3f glad_glRasterPos3f +typedef void (APIENTRYP PFNGLRASTERPOS3FVPROC)(const GLfloat *v); +GLAPI PFNGLRASTERPOS3FVPROC glad_glRasterPos3fv; +#define glRasterPos3fv glad_glRasterPos3fv +typedef void (APIENTRYP PFNGLRASTERPOS3IPROC)(GLint x, GLint y, GLint z); +GLAPI PFNGLRASTERPOS3IPROC glad_glRasterPos3i; +#define glRasterPos3i glad_glRasterPos3i +typedef void (APIENTRYP PFNGLRASTERPOS3IVPROC)(const GLint *v); +GLAPI PFNGLRASTERPOS3IVPROC glad_glRasterPos3iv; +#define glRasterPos3iv glad_glRasterPos3iv +typedef void (APIENTRYP PFNGLRASTERPOS3SPROC)(GLshort x, GLshort y, GLshort z); +GLAPI PFNGLRASTERPOS3SPROC glad_glRasterPos3s; +#define glRasterPos3s glad_glRasterPos3s +typedef void (APIENTRYP PFNGLRASTERPOS3SVPROC)(const GLshort *v); +GLAPI PFNGLRASTERPOS3SVPROC glad_glRasterPos3sv; +#define glRasterPos3sv glad_glRasterPos3sv +typedef void (APIENTRYP PFNGLRASTERPOS4DPROC)(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI PFNGLRASTERPOS4DPROC glad_glRasterPos4d; +#define glRasterPos4d glad_glRasterPos4d +typedef void (APIENTRYP PFNGLRASTERPOS4DVPROC)(const GLdouble *v); +GLAPI PFNGLRASTERPOS4DVPROC glad_glRasterPos4dv; +#define glRasterPos4dv glad_glRasterPos4dv +typedef void (APIENTRYP PFNGLRASTERPOS4FPROC)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI PFNGLRASTERPOS4FPROC glad_glRasterPos4f; +#define glRasterPos4f glad_glRasterPos4f +typedef void (APIENTRYP PFNGLRASTERPOS4FVPROC)(const GLfloat *v); +GLAPI PFNGLRASTERPOS4FVPROC glad_glRasterPos4fv; +#define glRasterPos4fv glad_glRasterPos4fv +typedef void (APIENTRYP PFNGLRASTERPOS4IPROC)(GLint x, GLint y, GLint z, GLint w); +GLAPI PFNGLRASTERPOS4IPROC glad_glRasterPos4i; +#define glRasterPos4i glad_glRasterPos4i +typedef void (APIENTRYP PFNGLRASTERPOS4IVPROC)(const GLint *v); +GLAPI PFNGLRASTERPOS4IVPROC glad_glRasterPos4iv; +#define glRasterPos4iv glad_glRasterPos4iv +typedef void (APIENTRYP PFNGLRASTERPOS4SPROC)(GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI PFNGLRASTERPOS4SPROC glad_glRasterPos4s; +#define glRasterPos4s glad_glRasterPos4s +typedef void (APIENTRYP PFNGLRASTERPOS4SVPROC)(const GLshort *v); +GLAPI PFNGLRASTERPOS4SVPROC glad_glRasterPos4sv; +#define glRasterPos4sv glad_glRasterPos4sv +typedef void (APIENTRYP PFNGLRECTDPROC)(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +GLAPI PFNGLRECTDPROC glad_glRectd; +#define glRectd glad_glRectd +typedef void (APIENTRYP PFNGLRECTDVPROC)(const GLdouble *v1, const GLdouble *v2); +GLAPI PFNGLRECTDVPROC glad_glRectdv; +#define glRectdv glad_glRectdv +typedef void (APIENTRYP PFNGLRECTFPROC)(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +GLAPI PFNGLRECTFPROC glad_glRectf; +#define glRectf glad_glRectf +typedef void (APIENTRYP PFNGLRECTFVPROC)(const GLfloat *v1, const GLfloat *v2); +GLAPI PFNGLRECTFVPROC glad_glRectfv; +#define glRectfv glad_glRectfv +typedef void (APIENTRYP PFNGLRECTIPROC)(GLint x1, GLint y1, GLint x2, GLint y2); +GLAPI PFNGLRECTIPROC glad_glRecti; +#define glRecti glad_glRecti +typedef void (APIENTRYP PFNGLRECTIVPROC)(const GLint *v1, const GLint *v2); +GLAPI PFNGLRECTIVPROC glad_glRectiv; +#define glRectiv glad_glRectiv +typedef void (APIENTRYP PFNGLRECTSPROC)(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +GLAPI PFNGLRECTSPROC glad_glRects; +#define glRects glad_glRects +typedef void (APIENTRYP PFNGLRECTSVPROC)(const GLshort *v1, const GLshort *v2); +GLAPI PFNGLRECTSVPROC glad_glRectsv; +#define glRectsv glad_glRectsv +typedef void (APIENTRYP PFNGLTEXCOORD1DPROC)(GLdouble s); +GLAPI PFNGLTEXCOORD1DPROC glad_glTexCoord1d; +#define glTexCoord1d glad_glTexCoord1d +typedef void (APIENTRYP PFNGLTEXCOORD1DVPROC)(const GLdouble *v); +GLAPI PFNGLTEXCOORD1DVPROC glad_glTexCoord1dv; +#define glTexCoord1dv glad_glTexCoord1dv +typedef void (APIENTRYP PFNGLTEXCOORD1FPROC)(GLfloat s); +GLAPI PFNGLTEXCOORD1FPROC glad_glTexCoord1f; +#define glTexCoord1f glad_glTexCoord1f +typedef void (APIENTRYP PFNGLTEXCOORD1FVPROC)(const GLfloat *v); +GLAPI PFNGLTEXCOORD1FVPROC glad_glTexCoord1fv; +#define glTexCoord1fv glad_glTexCoord1fv +typedef void (APIENTRYP PFNGLTEXCOORD1IPROC)(GLint s); +GLAPI PFNGLTEXCOORD1IPROC glad_glTexCoord1i; +#define glTexCoord1i glad_glTexCoord1i +typedef void (APIENTRYP PFNGLTEXCOORD1IVPROC)(const GLint *v); +GLAPI PFNGLTEXCOORD1IVPROC glad_glTexCoord1iv; +#define glTexCoord1iv glad_glTexCoord1iv +typedef void (APIENTRYP PFNGLTEXCOORD1SPROC)(GLshort s); +GLAPI PFNGLTEXCOORD1SPROC glad_glTexCoord1s; +#define glTexCoord1s glad_glTexCoord1s +typedef void (APIENTRYP PFNGLTEXCOORD1SVPROC)(const GLshort *v); +GLAPI PFNGLTEXCOORD1SVPROC glad_glTexCoord1sv; +#define glTexCoord1sv glad_glTexCoord1sv +typedef void (APIENTRYP PFNGLTEXCOORD2DPROC)(GLdouble s, GLdouble t); +GLAPI PFNGLTEXCOORD2DPROC glad_glTexCoord2d; +#define glTexCoord2d glad_glTexCoord2d +typedef void (APIENTRYP PFNGLTEXCOORD2DVPROC)(const GLdouble *v); +GLAPI PFNGLTEXCOORD2DVPROC glad_glTexCoord2dv; +#define glTexCoord2dv glad_glTexCoord2dv +typedef void (APIENTRYP PFNGLTEXCOORD2FPROC)(GLfloat s, GLfloat t); +GLAPI PFNGLTEXCOORD2FPROC glad_glTexCoord2f; +#define glTexCoord2f glad_glTexCoord2f +typedef void (APIENTRYP PFNGLTEXCOORD2FVPROC)(const GLfloat *v); +GLAPI PFNGLTEXCOORD2FVPROC glad_glTexCoord2fv; +#define glTexCoord2fv glad_glTexCoord2fv +typedef void (APIENTRYP PFNGLTEXCOORD2IPROC)(GLint s, GLint t); +GLAPI PFNGLTEXCOORD2IPROC glad_glTexCoord2i; +#define glTexCoord2i glad_glTexCoord2i +typedef void (APIENTRYP PFNGLTEXCOORD2IVPROC)(const GLint *v); +GLAPI PFNGLTEXCOORD2IVPROC glad_glTexCoord2iv; +#define glTexCoord2iv glad_glTexCoord2iv +typedef void (APIENTRYP PFNGLTEXCOORD2SPROC)(GLshort s, GLshort t); +GLAPI PFNGLTEXCOORD2SPROC glad_glTexCoord2s; +#define glTexCoord2s glad_glTexCoord2s +typedef void (APIENTRYP PFNGLTEXCOORD2SVPROC)(const GLshort *v); +GLAPI PFNGLTEXCOORD2SVPROC glad_glTexCoord2sv; +#define glTexCoord2sv glad_glTexCoord2sv +typedef void (APIENTRYP PFNGLTEXCOORD3DPROC)(GLdouble s, GLdouble t, GLdouble r); +GLAPI PFNGLTEXCOORD3DPROC glad_glTexCoord3d; +#define glTexCoord3d glad_glTexCoord3d +typedef void (APIENTRYP PFNGLTEXCOORD3DVPROC)(const GLdouble *v); +GLAPI PFNGLTEXCOORD3DVPROC glad_glTexCoord3dv; +#define glTexCoord3dv glad_glTexCoord3dv +typedef void (APIENTRYP PFNGLTEXCOORD3FPROC)(GLfloat s, GLfloat t, GLfloat r); +GLAPI PFNGLTEXCOORD3FPROC glad_glTexCoord3f; +#define glTexCoord3f glad_glTexCoord3f +typedef void (APIENTRYP PFNGLTEXCOORD3FVPROC)(const GLfloat *v); +GLAPI PFNGLTEXCOORD3FVPROC glad_glTexCoord3fv; +#define glTexCoord3fv glad_glTexCoord3fv +typedef void (APIENTRYP PFNGLTEXCOORD3IPROC)(GLint s, GLint t, GLint r); +GLAPI PFNGLTEXCOORD3IPROC glad_glTexCoord3i; +#define glTexCoord3i glad_glTexCoord3i +typedef void (APIENTRYP PFNGLTEXCOORD3IVPROC)(const GLint *v); +GLAPI PFNGLTEXCOORD3IVPROC glad_glTexCoord3iv; +#define glTexCoord3iv glad_glTexCoord3iv +typedef void (APIENTRYP PFNGLTEXCOORD3SPROC)(GLshort s, GLshort t, GLshort r); +GLAPI PFNGLTEXCOORD3SPROC glad_glTexCoord3s; +#define glTexCoord3s glad_glTexCoord3s +typedef void (APIENTRYP PFNGLTEXCOORD3SVPROC)(const GLshort *v); +GLAPI PFNGLTEXCOORD3SVPROC glad_glTexCoord3sv; +#define glTexCoord3sv glad_glTexCoord3sv +typedef void (APIENTRYP PFNGLTEXCOORD4DPROC)(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI PFNGLTEXCOORD4DPROC glad_glTexCoord4d; +#define glTexCoord4d glad_glTexCoord4d +typedef void (APIENTRYP PFNGLTEXCOORD4DVPROC)(const GLdouble *v); +GLAPI PFNGLTEXCOORD4DVPROC glad_glTexCoord4dv; +#define glTexCoord4dv glad_glTexCoord4dv +typedef void (APIENTRYP PFNGLTEXCOORD4FPROC)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI PFNGLTEXCOORD4FPROC glad_glTexCoord4f; +#define glTexCoord4f glad_glTexCoord4f +typedef void (APIENTRYP PFNGLTEXCOORD4FVPROC)(const GLfloat *v); +GLAPI PFNGLTEXCOORD4FVPROC glad_glTexCoord4fv; +#define glTexCoord4fv glad_glTexCoord4fv +typedef void (APIENTRYP PFNGLTEXCOORD4IPROC)(GLint s, GLint t, GLint r, GLint q); +GLAPI PFNGLTEXCOORD4IPROC glad_glTexCoord4i; +#define glTexCoord4i glad_glTexCoord4i +typedef void (APIENTRYP PFNGLTEXCOORD4IVPROC)(const GLint *v); +GLAPI PFNGLTEXCOORD4IVPROC glad_glTexCoord4iv; +#define glTexCoord4iv glad_glTexCoord4iv +typedef void (APIENTRYP PFNGLTEXCOORD4SPROC)(GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI PFNGLTEXCOORD4SPROC glad_glTexCoord4s; +#define glTexCoord4s glad_glTexCoord4s +typedef void (APIENTRYP PFNGLTEXCOORD4SVPROC)(const GLshort *v); +GLAPI PFNGLTEXCOORD4SVPROC glad_glTexCoord4sv; +#define glTexCoord4sv glad_glTexCoord4sv +typedef void (APIENTRYP PFNGLVERTEX2DPROC)(GLdouble x, GLdouble y); +GLAPI PFNGLVERTEX2DPROC glad_glVertex2d; +#define glVertex2d glad_glVertex2d +typedef void (APIENTRYP PFNGLVERTEX2DVPROC)(const GLdouble *v); +GLAPI PFNGLVERTEX2DVPROC glad_glVertex2dv; +#define glVertex2dv glad_glVertex2dv +typedef void (APIENTRYP PFNGLVERTEX2FPROC)(GLfloat x, GLfloat y); +GLAPI PFNGLVERTEX2FPROC glad_glVertex2f; +#define glVertex2f glad_glVertex2f +typedef void (APIENTRYP PFNGLVERTEX2FVPROC)(const GLfloat *v); +GLAPI PFNGLVERTEX2FVPROC glad_glVertex2fv; +#define glVertex2fv glad_glVertex2fv +typedef void (APIENTRYP PFNGLVERTEX2IPROC)(GLint x, GLint y); +GLAPI PFNGLVERTEX2IPROC glad_glVertex2i; +#define glVertex2i glad_glVertex2i +typedef void (APIENTRYP PFNGLVERTEX2IVPROC)(const GLint *v); +GLAPI PFNGLVERTEX2IVPROC glad_glVertex2iv; +#define glVertex2iv glad_glVertex2iv +typedef void (APIENTRYP PFNGLVERTEX2SPROC)(GLshort x, GLshort y); +GLAPI PFNGLVERTEX2SPROC glad_glVertex2s; +#define glVertex2s glad_glVertex2s +typedef void (APIENTRYP PFNGLVERTEX2SVPROC)(const GLshort *v); +GLAPI PFNGLVERTEX2SVPROC glad_glVertex2sv; +#define glVertex2sv glad_glVertex2sv +typedef void (APIENTRYP PFNGLVERTEX3DPROC)(GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLVERTEX3DPROC glad_glVertex3d; +#define glVertex3d glad_glVertex3d +typedef void (APIENTRYP PFNGLVERTEX3DVPROC)(const GLdouble *v); +GLAPI PFNGLVERTEX3DVPROC glad_glVertex3dv; +#define glVertex3dv glad_glVertex3dv +typedef void (APIENTRYP PFNGLVERTEX3FPROC)(GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLVERTEX3FPROC glad_glVertex3f; +#define glVertex3f glad_glVertex3f +typedef void (APIENTRYP PFNGLVERTEX3FVPROC)(const GLfloat *v); +GLAPI PFNGLVERTEX3FVPROC glad_glVertex3fv; +#define glVertex3fv glad_glVertex3fv +typedef void (APIENTRYP PFNGLVERTEX3IPROC)(GLint x, GLint y, GLint z); +GLAPI PFNGLVERTEX3IPROC glad_glVertex3i; +#define glVertex3i glad_glVertex3i +typedef void (APIENTRYP PFNGLVERTEX3IVPROC)(const GLint *v); +GLAPI PFNGLVERTEX3IVPROC glad_glVertex3iv; +#define glVertex3iv glad_glVertex3iv +typedef void (APIENTRYP PFNGLVERTEX3SPROC)(GLshort x, GLshort y, GLshort z); +GLAPI PFNGLVERTEX3SPROC glad_glVertex3s; +#define glVertex3s glad_glVertex3s +typedef void (APIENTRYP PFNGLVERTEX3SVPROC)(const GLshort *v); +GLAPI PFNGLVERTEX3SVPROC glad_glVertex3sv; +#define glVertex3sv glad_glVertex3sv +typedef void (APIENTRYP PFNGLVERTEX4DPROC)(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI PFNGLVERTEX4DPROC glad_glVertex4d; +#define glVertex4d glad_glVertex4d +typedef void (APIENTRYP PFNGLVERTEX4DVPROC)(const GLdouble *v); +GLAPI PFNGLVERTEX4DVPROC glad_glVertex4dv; +#define glVertex4dv glad_glVertex4dv +typedef void (APIENTRYP PFNGLVERTEX4FPROC)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI PFNGLVERTEX4FPROC glad_glVertex4f; +#define glVertex4f glad_glVertex4f +typedef void (APIENTRYP PFNGLVERTEX4FVPROC)(const GLfloat *v); +GLAPI PFNGLVERTEX4FVPROC glad_glVertex4fv; +#define glVertex4fv glad_glVertex4fv +typedef void (APIENTRYP PFNGLVERTEX4IPROC)(GLint x, GLint y, GLint z, GLint w); +GLAPI PFNGLVERTEX4IPROC glad_glVertex4i; +#define glVertex4i glad_glVertex4i +typedef void (APIENTRYP PFNGLVERTEX4IVPROC)(const GLint *v); +GLAPI PFNGLVERTEX4IVPROC glad_glVertex4iv; +#define glVertex4iv glad_glVertex4iv +typedef void (APIENTRYP PFNGLVERTEX4SPROC)(GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI PFNGLVERTEX4SPROC glad_glVertex4s; +#define glVertex4s glad_glVertex4s +typedef void (APIENTRYP PFNGLVERTEX4SVPROC)(const GLshort *v); +GLAPI PFNGLVERTEX4SVPROC glad_glVertex4sv; +#define glVertex4sv glad_glVertex4sv +typedef void (APIENTRYP PFNGLCLIPPLANEPROC)(GLenum plane, const GLdouble *equation); +GLAPI PFNGLCLIPPLANEPROC glad_glClipPlane; +#define glClipPlane glad_glClipPlane +typedef void (APIENTRYP PFNGLCOLORMATERIALPROC)(GLenum face, GLenum mode); +GLAPI PFNGLCOLORMATERIALPROC glad_glColorMaterial; +#define glColorMaterial glad_glColorMaterial +typedef void (APIENTRYP PFNGLFOGFPROC)(GLenum pname, GLfloat param); +GLAPI PFNGLFOGFPROC glad_glFogf; +#define glFogf glad_glFogf +typedef void (APIENTRYP PFNGLFOGFVPROC)(GLenum pname, const GLfloat *params); +GLAPI PFNGLFOGFVPROC glad_glFogfv; +#define glFogfv glad_glFogfv +typedef void (APIENTRYP PFNGLFOGIPROC)(GLenum pname, GLint param); +GLAPI PFNGLFOGIPROC glad_glFogi; +#define glFogi glad_glFogi +typedef void (APIENTRYP PFNGLFOGIVPROC)(GLenum pname, const GLint *params); +GLAPI PFNGLFOGIVPROC glad_glFogiv; +#define glFogiv glad_glFogiv +typedef void (APIENTRYP PFNGLLIGHTFPROC)(GLenum light, GLenum pname, GLfloat param); +GLAPI PFNGLLIGHTFPROC glad_glLightf; +#define glLightf glad_glLightf +typedef void (APIENTRYP PFNGLLIGHTFVPROC)(GLenum light, GLenum pname, const GLfloat *params); +GLAPI PFNGLLIGHTFVPROC glad_glLightfv; +#define glLightfv glad_glLightfv +typedef void (APIENTRYP PFNGLLIGHTIPROC)(GLenum light, GLenum pname, GLint param); +GLAPI PFNGLLIGHTIPROC glad_glLighti; +#define glLighti glad_glLighti +typedef void (APIENTRYP PFNGLLIGHTIVPROC)(GLenum light, GLenum pname, const GLint *params); +GLAPI PFNGLLIGHTIVPROC glad_glLightiv; +#define glLightiv glad_glLightiv +typedef void (APIENTRYP PFNGLLIGHTMODELFPROC)(GLenum pname, GLfloat param); +GLAPI PFNGLLIGHTMODELFPROC glad_glLightModelf; +#define glLightModelf glad_glLightModelf +typedef void (APIENTRYP PFNGLLIGHTMODELFVPROC)(GLenum pname, const GLfloat *params); +GLAPI PFNGLLIGHTMODELFVPROC glad_glLightModelfv; +#define glLightModelfv glad_glLightModelfv +typedef void (APIENTRYP PFNGLLIGHTMODELIPROC)(GLenum pname, GLint param); +GLAPI PFNGLLIGHTMODELIPROC glad_glLightModeli; +#define glLightModeli glad_glLightModeli +typedef void (APIENTRYP PFNGLLIGHTMODELIVPROC)(GLenum pname, const GLint *params); +GLAPI PFNGLLIGHTMODELIVPROC glad_glLightModeliv; +#define glLightModeliv glad_glLightModeliv +typedef void (APIENTRYP PFNGLLINESTIPPLEPROC)(GLint factor, GLushort pattern); +GLAPI PFNGLLINESTIPPLEPROC glad_glLineStipple; +#define glLineStipple glad_glLineStipple +typedef void (APIENTRYP PFNGLMATERIALFPROC)(GLenum face, GLenum pname, GLfloat param); +GLAPI PFNGLMATERIALFPROC glad_glMaterialf; +#define glMaterialf glad_glMaterialf +typedef void (APIENTRYP PFNGLMATERIALFVPROC)(GLenum face, GLenum pname, const GLfloat *params); +GLAPI PFNGLMATERIALFVPROC glad_glMaterialfv; +#define glMaterialfv glad_glMaterialfv +typedef void (APIENTRYP PFNGLMATERIALIPROC)(GLenum face, GLenum pname, GLint param); +GLAPI PFNGLMATERIALIPROC glad_glMateriali; +#define glMateriali glad_glMateriali +typedef void (APIENTRYP PFNGLMATERIALIVPROC)(GLenum face, GLenum pname, const GLint *params); +GLAPI PFNGLMATERIALIVPROC glad_glMaterialiv; +#define glMaterialiv glad_glMaterialiv +typedef void (APIENTRYP PFNGLPOLYGONSTIPPLEPROC)(const GLubyte *mask); +GLAPI PFNGLPOLYGONSTIPPLEPROC glad_glPolygonStipple; +#define glPolygonStipple glad_glPolygonStipple +typedef void (APIENTRYP PFNGLSHADEMODELPROC)(GLenum mode); +GLAPI PFNGLSHADEMODELPROC glad_glShadeModel; +#define glShadeModel glad_glShadeModel +typedef void (APIENTRYP PFNGLTEXENVFPROC)(GLenum target, GLenum pname, GLfloat param); +GLAPI PFNGLTEXENVFPROC glad_glTexEnvf; +#define glTexEnvf glad_glTexEnvf +typedef void (APIENTRYP PFNGLTEXENVFVPROC)(GLenum target, GLenum pname, const GLfloat *params); +GLAPI PFNGLTEXENVFVPROC glad_glTexEnvfv; +#define glTexEnvfv glad_glTexEnvfv +typedef void (APIENTRYP PFNGLTEXENVIPROC)(GLenum target, GLenum pname, GLint param); +GLAPI PFNGLTEXENVIPROC glad_glTexEnvi; +#define glTexEnvi glad_glTexEnvi +typedef void (APIENTRYP PFNGLTEXENVIVPROC)(GLenum target, GLenum pname, const GLint *params); +GLAPI PFNGLTEXENVIVPROC glad_glTexEnviv; +#define glTexEnviv glad_glTexEnviv +typedef void (APIENTRYP PFNGLTEXGENDPROC)(GLenum coord, GLenum pname, GLdouble param); +GLAPI PFNGLTEXGENDPROC glad_glTexGend; +#define glTexGend glad_glTexGend +typedef void (APIENTRYP PFNGLTEXGENDVPROC)(GLenum coord, GLenum pname, const GLdouble *params); +GLAPI PFNGLTEXGENDVPROC glad_glTexGendv; +#define glTexGendv glad_glTexGendv +typedef void (APIENTRYP PFNGLTEXGENFPROC)(GLenum coord, GLenum pname, GLfloat param); +GLAPI PFNGLTEXGENFPROC glad_glTexGenf; +#define glTexGenf glad_glTexGenf +typedef void (APIENTRYP PFNGLTEXGENFVPROC)(GLenum coord, GLenum pname, const GLfloat *params); +GLAPI PFNGLTEXGENFVPROC glad_glTexGenfv; +#define glTexGenfv glad_glTexGenfv +typedef void (APIENTRYP PFNGLTEXGENIPROC)(GLenum coord, GLenum pname, GLint param); +GLAPI PFNGLTEXGENIPROC glad_glTexGeni; +#define glTexGeni glad_glTexGeni +typedef void (APIENTRYP PFNGLTEXGENIVPROC)(GLenum coord, GLenum pname, const GLint *params); +GLAPI PFNGLTEXGENIVPROC glad_glTexGeniv; +#define glTexGeniv glad_glTexGeniv +typedef void (APIENTRYP PFNGLFEEDBACKBUFFERPROC)(GLsizei size, GLenum type, GLfloat *buffer); +GLAPI PFNGLFEEDBACKBUFFERPROC glad_glFeedbackBuffer; +#define glFeedbackBuffer glad_glFeedbackBuffer +typedef void (APIENTRYP PFNGLSELECTBUFFERPROC)(GLsizei size, GLuint *buffer); +GLAPI PFNGLSELECTBUFFERPROC glad_glSelectBuffer; +#define glSelectBuffer glad_glSelectBuffer +typedef GLint (APIENTRYP PFNGLRENDERMODEPROC)(GLenum mode); +GLAPI PFNGLRENDERMODEPROC glad_glRenderMode; +#define glRenderMode glad_glRenderMode +typedef void (APIENTRYP PFNGLINITNAMESPROC)(void); +GLAPI PFNGLINITNAMESPROC glad_glInitNames; +#define glInitNames glad_glInitNames +typedef void (APIENTRYP PFNGLLOADNAMEPROC)(GLuint name); +GLAPI PFNGLLOADNAMEPROC glad_glLoadName; +#define glLoadName glad_glLoadName +typedef void (APIENTRYP PFNGLPASSTHROUGHPROC)(GLfloat token); +GLAPI PFNGLPASSTHROUGHPROC glad_glPassThrough; +#define glPassThrough glad_glPassThrough +typedef void (APIENTRYP PFNGLPOPNAMEPROC)(void); +GLAPI PFNGLPOPNAMEPROC glad_glPopName; +#define glPopName glad_glPopName +typedef void (APIENTRYP PFNGLPUSHNAMEPROC)(GLuint name); +GLAPI PFNGLPUSHNAMEPROC glad_glPushName; +#define glPushName glad_glPushName +typedef void (APIENTRYP PFNGLCLEARACCUMPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI PFNGLCLEARACCUMPROC glad_glClearAccum; +#define glClearAccum glad_glClearAccum +typedef void (APIENTRYP PFNGLCLEARINDEXPROC)(GLfloat c); +GLAPI PFNGLCLEARINDEXPROC glad_glClearIndex; +#define glClearIndex glad_glClearIndex +typedef void (APIENTRYP PFNGLINDEXMASKPROC)(GLuint mask); +GLAPI PFNGLINDEXMASKPROC glad_glIndexMask; +#define glIndexMask glad_glIndexMask +typedef void (APIENTRYP PFNGLACCUMPROC)(GLenum op, GLfloat value); +GLAPI PFNGLACCUMPROC glad_glAccum; +#define glAccum glad_glAccum +typedef void (APIENTRYP PFNGLPOPATTRIBPROC)(void); +GLAPI PFNGLPOPATTRIBPROC glad_glPopAttrib; +#define glPopAttrib glad_glPopAttrib +typedef void (APIENTRYP PFNGLPUSHATTRIBPROC)(GLbitfield mask); +GLAPI PFNGLPUSHATTRIBPROC glad_glPushAttrib; +#define glPushAttrib glad_glPushAttrib +typedef void (APIENTRYP PFNGLMAP1DPROC)(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI PFNGLMAP1DPROC glad_glMap1d; +#define glMap1d glad_glMap1d +typedef void (APIENTRYP PFNGLMAP1FPROC)(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI PFNGLMAP1FPROC glad_glMap1f; +#define glMap1f glad_glMap1f +typedef void (APIENTRYP PFNGLMAP2DPROC)(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI PFNGLMAP2DPROC glad_glMap2d; +#define glMap2d glad_glMap2d +typedef void (APIENTRYP PFNGLMAP2FPROC)(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +GLAPI PFNGLMAP2FPROC glad_glMap2f; +#define glMap2f glad_glMap2f +typedef void (APIENTRYP PFNGLMAPGRID1DPROC)(GLint un, GLdouble u1, GLdouble u2); +GLAPI PFNGLMAPGRID1DPROC glad_glMapGrid1d; +#define glMapGrid1d glad_glMapGrid1d +typedef void (APIENTRYP PFNGLMAPGRID1FPROC)(GLint un, GLfloat u1, GLfloat u2); +GLAPI PFNGLMAPGRID1FPROC glad_glMapGrid1f; +#define glMapGrid1f glad_glMapGrid1f +typedef void (APIENTRYP PFNGLMAPGRID2DPROC)(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +GLAPI PFNGLMAPGRID2DPROC glad_glMapGrid2d; +#define glMapGrid2d glad_glMapGrid2d +typedef void (APIENTRYP PFNGLMAPGRID2FPROC)(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +GLAPI PFNGLMAPGRID2FPROC glad_glMapGrid2f; +#define glMapGrid2f glad_glMapGrid2f +typedef void (APIENTRYP PFNGLEVALCOORD1DPROC)(GLdouble u); +GLAPI PFNGLEVALCOORD1DPROC glad_glEvalCoord1d; +#define glEvalCoord1d glad_glEvalCoord1d +typedef void (APIENTRYP PFNGLEVALCOORD1DVPROC)(const GLdouble *u); +GLAPI PFNGLEVALCOORD1DVPROC glad_glEvalCoord1dv; +#define glEvalCoord1dv glad_glEvalCoord1dv +typedef void (APIENTRYP PFNGLEVALCOORD1FPROC)(GLfloat u); +GLAPI PFNGLEVALCOORD1FPROC glad_glEvalCoord1f; +#define glEvalCoord1f glad_glEvalCoord1f +typedef void (APIENTRYP PFNGLEVALCOORD1FVPROC)(const GLfloat *u); +GLAPI PFNGLEVALCOORD1FVPROC glad_glEvalCoord1fv; +#define glEvalCoord1fv glad_glEvalCoord1fv +typedef void (APIENTRYP PFNGLEVALCOORD2DPROC)(GLdouble u, GLdouble v); +GLAPI PFNGLEVALCOORD2DPROC glad_glEvalCoord2d; +#define glEvalCoord2d glad_glEvalCoord2d +typedef void (APIENTRYP PFNGLEVALCOORD2DVPROC)(const GLdouble *u); +GLAPI PFNGLEVALCOORD2DVPROC glad_glEvalCoord2dv; +#define glEvalCoord2dv glad_glEvalCoord2dv +typedef void (APIENTRYP PFNGLEVALCOORD2FPROC)(GLfloat u, GLfloat v); +GLAPI PFNGLEVALCOORD2FPROC glad_glEvalCoord2f; +#define glEvalCoord2f glad_glEvalCoord2f +typedef void (APIENTRYP PFNGLEVALCOORD2FVPROC)(const GLfloat *u); +GLAPI PFNGLEVALCOORD2FVPROC glad_glEvalCoord2fv; +#define glEvalCoord2fv glad_glEvalCoord2fv +typedef void (APIENTRYP PFNGLEVALMESH1PROC)(GLenum mode, GLint i1, GLint i2); +GLAPI PFNGLEVALMESH1PROC glad_glEvalMesh1; +#define glEvalMesh1 glad_glEvalMesh1 +typedef void (APIENTRYP PFNGLEVALPOINT1PROC)(GLint i); +GLAPI PFNGLEVALPOINT1PROC glad_glEvalPoint1; +#define glEvalPoint1 glad_glEvalPoint1 +typedef void (APIENTRYP PFNGLEVALMESH2PROC)(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +GLAPI PFNGLEVALMESH2PROC glad_glEvalMesh2; +#define glEvalMesh2 glad_glEvalMesh2 +typedef void (APIENTRYP PFNGLEVALPOINT2PROC)(GLint i, GLint j); +GLAPI PFNGLEVALPOINT2PROC glad_glEvalPoint2; +#define glEvalPoint2 glad_glEvalPoint2 +typedef void (APIENTRYP PFNGLALPHAFUNCPROC)(GLenum func, GLfloat ref); +GLAPI PFNGLALPHAFUNCPROC glad_glAlphaFunc; +#define glAlphaFunc glad_glAlphaFunc +typedef void (APIENTRYP PFNGLPIXELZOOMPROC)(GLfloat xfactor, GLfloat yfactor); +GLAPI PFNGLPIXELZOOMPROC glad_glPixelZoom; +#define glPixelZoom glad_glPixelZoom +typedef void (APIENTRYP PFNGLPIXELTRANSFERFPROC)(GLenum pname, GLfloat param); +GLAPI PFNGLPIXELTRANSFERFPROC glad_glPixelTransferf; +#define glPixelTransferf glad_glPixelTransferf +typedef void (APIENTRYP PFNGLPIXELTRANSFERIPROC)(GLenum pname, GLint param); +GLAPI PFNGLPIXELTRANSFERIPROC glad_glPixelTransferi; +#define glPixelTransferi glad_glPixelTransferi +typedef void (APIENTRYP PFNGLPIXELMAPFVPROC)(GLenum map, GLsizei mapsize, const GLfloat *values); +GLAPI PFNGLPIXELMAPFVPROC glad_glPixelMapfv; +#define glPixelMapfv glad_glPixelMapfv +typedef void (APIENTRYP PFNGLPIXELMAPUIVPROC)(GLenum map, GLsizei mapsize, const GLuint *values); +GLAPI PFNGLPIXELMAPUIVPROC glad_glPixelMapuiv; +#define glPixelMapuiv glad_glPixelMapuiv +typedef void (APIENTRYP PFNGLPIXELMAPUSVPROC)(GLenum map, GLsizei mapsize, const GLushort *values); +GLAPI PFNGLPIXELMAPUSVPROC glad_glPixelMapusv; +#define glPixelMapusv glad_glPixelMapusv +typedef void (APIENTRYP PFNGLCOPYPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +GLAPI PFNGLCOPYPIXELSPROC glad_glCopyPixels; +#define glCopyPixels glad_glCopyPixels +typedef void (APIENTRYP PFNGLDRAWPIXELSPROC)(GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLDRAWPIXELSPROC glad_glDrawPixels; +#define glDrawPixels glad_glDrawPixels +typedef void (APIENTRYP PFNGLGETCLIPPLANEPROC)(GLenum plane, GLdouble *equation); +GLAPI PFNGLGETCLIPPLANEPROC glad_glGetClipPlane; +#define glGetClipPlane glad_glGetClipPlane +typedef void (APIENTRYP PFNGLGETLIGHTFVPROC)(GLenum light, GLenum pname, GLfloat *params); +GLAPI PFNGLGETLIGHTFVPROC glad_glGetLightfv; +#define glGetLightfv glad_glGetLightfv +typedef void (APIENTRYP PFNGLGETLIGHTIVPROC)(GLenum light, GLenum pname, GLint *params); +GLAPI PFNGLGETLIGHTIVPROC glad_glGetLightiv; +#define glGetLightiv glad_glGetLightiv +typedef void (APIENTRYP PFNGLGETMAPDVPROC)(GLenum target, GLenum query, GLdouble *v); +GLAPI PFNGLGETMAPDVPROC glad_glGetMapdv; +#define glGetMapdv glad_glGetMapdv +typedef void (APIENTRYP PFNGLGETMAPFVPROC)(GLenum target, GLenum query, GLfloat *v); +GLAPI PFNGLGETMAPFVPROC glad_glGetMapfv; +#define glGetMapfv glad_glGetMapfv +typedef void (APIENTRYP PFNGLGETMAPIVPROC)(GLenum target, GLenum query, GLint *v); +GLAPI PFNGLGETMAPIVPROC glad_glGetMapiv; +#define glGetMapiv glad_glGetMapiv +typedef void (APIENTRYP PFNGLGETMATERIALFVPROC)(GLenum face, GLenum pname, GLfloat *params); +GLAPI PFNGLGETMATERIALFVPROC glad_glGetMaterialfv; +#define glGetMaterialfv glad_glGetMaterialfv +typedef void (APIENTRYP PFNGLGETMATERIALIVPROC)(GLenum face, GLenum pname, GLint *params); +GLAPI PFNGLGETMATERIALIVPROC glad_glGetMaterialiv; +#define glGetMaterialiv glad_glGetMaterialiv +typedef void (APIENTRYP PFNGLGETPIXELMAPFVPROC)(GLenum map, GLfloat *values); +GLAPI PFNGLGETPIXELMAPFVPROC glad_glGetPixelMapfv; +#define glGetPixelMapfv glad_glGetPixelMapfv +typedef void (APIENTRYP PFNGLGETPIXELMAPUIVPROC)(GLenum map, GLuint *values); +GLAPI PFNGLGETPIXELMAPUIVPROC glad_glGetPixelMapuiv; +#define glGetPixelMapuiv glad_glGetPixelMapuiv +typedef void (APIENTRYP PFNGLGETPIXELMAPUSVPROC)(GLenum map, GLushort *values); +GLAPI PFNGLGETPIXELMAPUSVPROC glad_glGetPixelMapusv; +#define glGetPixelMapusv glad_glGetPixelMapusv +typedef void (APIENTRYP PFNGLGETPOLYGONSTIPPLEPROC)(GLubyte *mask); +GLAPI PFNGLGETPOLYGONSTIPPLEPROC glad_glGetPolygonStipple; +#define glGetPolygonStipple glad_glGetPolygonStipple +typedef void (APIENTRYP PFNGLGETTEXENVFVPROC)(GLenum target, GLenum pname, GLfloat *params); +GLAPI PFNGLGETTEXENVFVPROC glad_glGetTexEnvfv; +#define glGetTexEnvfv glad_glGetTexEnvfv +typedef void (APIENTRYP PFNGLGETTEXENVIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETTEXENVIVPROC glad_glGetTexEnviv; +#define glGetTexEnviv glad_glGetTexEnviv +typedef void (APIENTRYP PFNGLGETTEXGENDVPROC)(GLenum coord, GLenum pname, GLdouble *params); +GLAPI PFNGLGETTEXGENDVPROC glad_glGetTexGendv; +#define glGetTexGendv glad_glGetTexGendv +typedef void (APIENTRYP PFNGLGETTEXGENFVPROC)(GLenum coord, GLenum pname, GLfloat *params); +GLAPI PFNGLGETTEXGENFVPROC glad_glGetTexGenfv; +#define glGetTexGenfv glad_glGetTexGenfv +typedef void (APIENTRYP PFNGLGETTEXGENIVPROC)(GLenum coord, GLenum pname, GLint *params); +GLAPI PFNGLGETTEXGENIVPROC glad_glGetTexGeniv; +#define glGetTexGeniv glad_glGetTexGeniv +typedef GLboolean (APIENTRYP PFNGLISLISTPROC)(GLuint list); +GLAPI PFNGLISLISTPROC glad_glIsList; +#define glIsList glad_glIsList +typedef void (APIENTRYP PFNGLFRUSTUMPROC)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI PFNGLFRUSTUMPROC glad_glFrustum; +#define glFrustum glad_glFrustum +typedef void (APIENTRYP PFNGLLOADIDENTITYPROC)(void); +GLAPI PFNGLLOADIDENTITYPROC glad_glLoadIdentity; +#define glLoadIdentity glad_glLoadIdentity +typedef void (APIENTRYP PFNGLLOADMATRIXFPROC)(const GLfloat *m); +GLAPI PFNGLLOADMATRIXFPROC glad_glLoadMatrixf; +#define glLoadMatrixf glad_glLoadMatrixf +typedef void (APIENTRYP PFNGLLOADMATRIXDPROC)(const GLdouble *m); +GLAPI PFNGLLOADMATRIXDPROC glad_glLoadMatrixd; +#define glLoadMatrixd glad_glLoadMatrixd +typedef void (APIENTRYP PFNGLMATRIXMODEPROC)(GLenum mode); +GLAPI PFNGLMATRIXMODEPROC glad_glMatrixMode; +#define glMatrixMode glad_glMatrixMode +typedef void (APIENTRYP PFNGLMULTMATRIXFPROC)(const GLfloat *m); +GLAPI PFNGLMULTMATRIXFPROC glad_glMultMatrixf; +#define glMultMatrixf glad_glMultMatrixf +typedef void (APIENTRYP PFNGLMULTMATRIXDPROC)(const GLdouble *m); +GLAPI PFNGLMULTMATRIXDPROC glad_glMultMatrixd; +#define glMultMatrixd glad_glMultMatrixd +typedef void (APIENTRYP PFNGLORTHOPROC)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI PFNGLORTHOPROC glad_glOrtho; +#define glOrtho glad_glOrtho +typedef void (APIENTRYP PFNGLPOPMATRIXPROC)(void); +GLAPI PFNGLPOPMATRIXPROC glad_glPopMatrix; +#define glPopMatrix glad_glPopMatrix +typedef void (APIENTRYP PFNGLPUSHMATRIXPROC)(void); +GLAPI PFNGLPUSHMATRIXPROC glad_glPushMatrix; +#define glPushMatrix glad_glPushMatrix +typedef void (APIENTRYP PFNGLROTATEDPROC)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLROTATEDPROC glad_glRotated; +#define glRotated glad_glRotated +typedef void (APIENTRYP PFNGLROTATEFPROC)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLROTATEFPROC glad_glRotatef; +#define glRotatef glad_glRotatef +typedef void (APIENTRYP PFNGLSCALEDPROC)(GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLSCALEDPROC glad_glScaled; +#define glScaled glad_glScaled +typedef void (APIENTRYP PFNGLSCALEFPROC)(GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLSCALEFPROC glad_glScalef; +#define glScalef glad_glScalef +typedef void (APIENTRYP PFNGLTRANSLATEDPROC)(GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLTRANSLATEDPROC glad_glTranslated; +#define glTranslated glad_glTranslated +typedef void (APIENTRYP PFNGLTRANSLATEFPROC)(GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLTRANSLATEFPROC glad_glTranslatef; +#define glTranslatef glad_glTranslatef +#endif +#ifndef GL_VERSION_1_1 +#define GL_VERSION_1_1 1 +GLAPI int GLAD_GL_VERSION_1_1; +typedef void (APIENTRYP PFNGLDRAWARRAYSPROC)(GLenum mode, GLint first, GLsizei count); +GLAPI PFNGLDRAWARRAYSPROC glad_glDrawArrays; +#define glDrawArrays glad_glDrawArrays +typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const void *indices); +GLAPI PFNGLDRAWELEMENTSPROC glad_glDrawElements; +#define glDrawElements glad_glDrawElements +typedef void (APIENTRYP PFNGLGETPOINTERVPROC)(GLenum pname, void **params); +GLAPI PFNGLGETPOINTERVPROC glad_glGetPointerv; +#define glGetPointerv glad_glGetPointerv +typedef void (APIENTRYP PFNGLPOLYGONOFFSETPROC)(GLfloat factor, GLfloat units); +GLAPI PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset; +#define glPolygonOffset glad_glPolygonOffset +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI PFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D; +#define glCopyTexImage1D glad_glCopyTexImage1D +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI PFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D; +#define glCopyTexImage2D glad_glCopyTexImage2D +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI PFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D; +#define glCopyTexSubImage1D glad_glCopyTexSubImage1D +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D; +#define glCopyTexSubImage2D glad_glCopyTexSubImage2D +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D; +#define glTexSubImage1D glad_glTexSubImage1D +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D; +#define glTexSubImage2D glad_glTexSubImage2D +typedef void (APIENTRYP PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture); +GLAPI PFNGLBINDTEXTUREPROC glad_glBindTexture; +#define glBindTexture glad_glBindTexture +typedef void (APIENTRYP PFNGLDELETETEXTURESPROC)(GLsizei n, const GLuint *textures); +GLAPI PFNGLDELETETEXTURESPROC glad_glDeleteTextures; +#define glDeleteTextures glad_glDeleteTextures +typedef void (APIENTRYP PFNGLGENTEXTURESPROC)(GLsizei n, GLuint *textures); +GLAPI PFNGLGENTEXTURESPROC glad_glGenTextures; +#define glGenTextures glad_glGenTextures +typedef GLboolean (APIENTRYP PFNGLISTEXTUREPROC)(GLuint texture); +GLAPI PFNGLISTEXTUREPROC glad_glIsTexture; +#define glIsTexture glad_glIsTexture +typedef void (APIENTRYP PFNGLARRAYELEMENTPROC)(GLint i); +GLAPI PFNGLARRAYELEMENTPROC glad_glArrayElement; +#define glArrayElement glad_glArrayElement +typedef void (APIENTRYP PFNGLCOLORPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLCOLORPOINTERPROC glad_glColorPointer; +#define glColorPointer glad_glColorPointer +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEPROC)(GLenum array); +GLAPI PFNGLDISABLECLIENTSTATEPROC glad_glDisableClientState; +#define glDisableClientState glad_glDisableClientState +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERPROC)(GLsizei stride, const void *pointer); +GLAPI PFNGLEDGEFLAGPOINTERPROC glad_glEdgeFlagPointer; +#define glEdgeFlagPointer glad_glEdgeFlagPointer +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEPROC)(GLenum array); +GLAPI PFNGLENABLECLIENTSTATEPROC glad_glEnableClientState; +#define glEnableClientState glad_glEnableClientState +typedef void (APIENTRYP PFNGLINDEXPOINTERPROC)(GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLINDEXPOINTERPROC glad_glIndexPointer; +#define glIndexPointer glad_glIndexPointer +typedef void (APIENTRYP PFNGLINTERLEAVEDARRAYSPROC)(GLenum format, GLsizei stride, const void *pointer); +GLAPI PFNGLINTERLEAVEDARRAYSPROC glad_glInterleavedArrays; +#define glInterleavedArrays glad_glInterleavedArrays +typedef void (APIENTRYP PFNGLNORMALPOINTERPROC)(GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLNORMALPOINTERPROC glad_glNormalPointer; +#define glNormalPointer glad_glNormalPointer +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLTEXCOORDPOINTERPROC glad_glTexCoordPointer; +#define glTexCoordPointer glad_glTexCoordPointer +typedef void (APIENTRYP PFNGLVERTEXPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLVERTEXPOINTERPROC glad_glVertexPointer; +#define glVertexPointer glad_glVertexPointer +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTPROC)(GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI PFNGLARETEXTURESRESIDENTPROC glad_glAreTexturesResident; +#define glAreTexturesResident glad_glAreTexturesResident +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESPROC)(GLsizei n, const GLuint *textures, const GLfloat *priorities); +GLAPI PFNGLPRIORITIZETEXTURESPROC glad_glPrioritizeTextures; +#define glPrioritizeTextures glad_glPrioritizeTextures +typedef void (APIENTRYP PFNGLINDEXUBPROC)(GLubyte c); +GLAPI PFNGLINDEXUBPROC glad_glIndexub; +#define glIndexub glad_glIndexub +typedef void (APIENTRYP PFNGLINDEXUBVPROC)(const GLubyte *c); +GLAPI PFNGLINDEXUBVPROC glad_glIndexubv; +#define glIndexubv glad_glIndexubv +typedef void (APIENTRYP PFNGLPOPCLIENTATTRIBPROC)(void); +GLAPI PFNGLPOPCLIENTATTRIBPROC glad_glPopClientAttrib; +#define glPopClientAttrib glad_glPopClientAttrib +typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBPROC)(GLbitfield mask); +GLAPI PFNGLPUSHCLIENTATTRIBPROC glad_glPushClientAttrib; +#define glPushClientAttrib glad_glPushClientAttrib +#endif +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +GLAPI int GLAD_GL_VERSION_1_2; +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +GLAPI PFNGLDRAWRANGEELEMENTSPROC glad_glDrawRangeElements; +#define glDrawRangeElements glad_glDrawRangeElements +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXIMAGE3DPROC glad_glTexImage3D; +#define glTexImage3D glad_glTexImage3D +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D; +#define glTexSubImage3D glad_glTexSubImage3D +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D; +#define glCopyTexSubImage3D glad_glCopyTexSubImage3D +#endif +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +GLAPI int GLAD_GL_VERSION_1_3; +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC)(GLenum texture); +GLAPI PFNGLACTIVETEXTUREPROC glad_glActiveTexture; +#define glActiveTexture glad_glActiveTexture +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC)(GLfloat value, GLboolean invert); +GLAPI PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage; +#define glSampleCoverage glad_glSampleCoverage +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D; +#define glCompressedTexImage3D glad_glCompressedTexImage3D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D; +#define glCompressedTexImage2D glad_glCompressedTexImage2D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D; +#define glCompressedTexImage1D glad_glCompressedTexImage1D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D; +#define glCompressedTexSubImage3D glad_glCompressedTexSubImage3D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D; +#define glCompressedTexSubImage2D glad_glCompressedTexSubImage2D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D; +#define glCompressedTexSubImage1D glad_glCompressedTexSubImage1D +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint level, void *img); +GLAPI PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage; +#define glGetCompressedTexImage glad_glGetCompressedTexImage +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC)(GLenum texture); +GLAPI PFNGLCLIENTACTIVETEXTUREPROC glad_glClientActiveTexture; +#define glClientActiveTexture glad_glClientActiveTexture +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC)(GLenum target, GLdouble s); +GLAPI PFNGLMULTITEXCOORD1DPROC glad_glMultiTexCoord1d; +#define glMultiTexCoord1d glad_glMultiTexCoord1d +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC)(GLenum target, const GLdouble *v); +GLAPI PFNGLMULTITEXCOORD1DVPROC glad_glMultiTexCoord1dv; +#define glMultiTexCoord1dv glad_glMultiTexCoord1dv +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC)(GLenum target, GLfloat s); +GLAPI PFNGLMULTITEXCOORD1FPROC glad_glMultiTexCoord1f; +#define glMultiTexCoord1f glad_glMultiTexCoord1f +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC)(GLenum target, const GLfloat *v); +GLAPI PFNGLMULTITEXCOORD1FVPROC glad_glMultiTexCoord1fv; +#define glMultiTexCoord1fv glad_glMultiTexCoord1fv +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC)(GLenum target, GLint s); +GLAPI PFNGLMULTITEXCOORD1IPROC glad_glMultiTexCoord1i; +#define glMultiTexCoord1i glad_glMultiTexCoord1i +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC)(GLenum target, const GLint *v); +GLAPI PFNGLMULTITEXCOORD1IVPROC glad_glMultiTexCoord1iv; +#define glMultiTexCoord1iv glad_glMultiTexCoord1iv +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC)(GLenum target, GLshort s); +GLAPI PFNGLMULTITEXCOORD1SPROC glad_glMultiTexCoord1s; +#define glMultiTexCoord1s glad_glMultiTexCoord1s +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC)(GLenum target, const GLshort *v); +GLAPI PFNGLMULTITEXCOORD1SVPROC glad_glMultiTexCoord1sv; +#define glMultiTexCoord1sv glad_glMultiTexCoord1sv +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC)(GLenum target, GLdouble s, GLdouble t); +GLAPI PFNGLMULTITEXCOORD2DPROC glad_glMultiTexCoord2d; +#define glMultiTexCoord2d glad_glMultiTexCoord2d +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC)(GLenum target, const GLdouble *v); +GLAPI PFNGLMULTITEXCOORD2DVPROC glad_glMultiTexCoord2dv; +#define glMultiTexCoord2dv glad_glMultiTexCoord2dv +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC)(GLenum target, GLfloat s, GLfloat t); +GLAPI PFNGLMULTITEXCOORD2FPROC glad_glMultiTexCoord2f; +#define glMultiTexCoord2f glad_glMultiTexCoord2f +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC)(GLenum target, const GLfloat *v); +GLAPI PFNGLMULTITEXCOORD2FVPROC glad_glMultiTexCoord2fv; +#define glMultiTexCoord2fv glad_glMultiTexCoord2fv +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC)(GLenum target, GLint s, GLint t); +GLAPI PFNGLMULTITEXCOORD2IPROC glad_glMultiTexCoord2i; +#define glMultiTexCoord2i glad_glMultiTexCoord2i +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC)(GLenum target, const GLint *v); +GLAPI PFNGLMULTITEXCOORD2IVPROC glad_glMultiTexCoord2iv; +#define glMultiTexCoord2iv glad_glMultiTexCoord2iv +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC)(GLenum target, GLshort s, GLshort t); +GLAPI PFNGLMULTITEXCOORD2SPROC glad_glMultiTexCoord2s; +#define glMultiTexCoord2s glad_glMultiTexCoord2s +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC)(GLenum target, const GLshort *v); +GLAPI PFNGLMULTITEXCOORD2SVPROC glad_glMultiTexCoord2sv; +#define glMultiTexCoord2sv glad_glMultiTexCoord2sv +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC)(GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI PFNGLMULTITEXCOORD3DPROC glad_glMultiTexCoord3d; +#define glMultiTexCoord3d glad_glMultiTexCoord3d +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC)(GLenum target, const GLdouble *v); +GLAPI PFNGLMULTITEXCOORD3DVPROC glad_glMultiTexCoord3dv; +#define glMultiTexCoord3dv glad_glMultiTexCoord3dv +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC)(GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI PFNGLMULTITEXCOORD3FPROC glad_glMultiTexCoord3f; +#define glMultiTexCoord3f glad_glMultiTexCoord3f +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC)(GLenum target, const GLfloat *v); +GLAPI PFNGLMULTITEXCOORD3FVPROC glad_glMultiTexCoord3fv; +#define glMultiTexCoord3fv glad_glMultiTexCoord3fv +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC)(GLenum target, GLint s, GLint t, GLint r); +GLAPI PFNGLMULTITEXCOORD3IPROC glad_glMultiTexCoord3i; +#define glMultiTexCoord3i glad_glMultiTexCoord3i +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC)(GLenum target, const GLint *v); +GLAPI PFNGLMULTITEXCOORD3IVPROC glad_glMultiTexCoord3iv; +#define glMultiTexCoord3iv glad_glMultiTexCoord3iv +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC)(GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI PFNGLMULTITEXCOORD3SPROC glad_glMultiTexCoord3s; +#define glMultiTexCoord3s glad_glMultiTexCoord3s +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC)(GLenum target, const GLshort *v); +GLAPI PFNGLMULTITEXCOORD3SVPROC glad_glMultiTexCoord3sv; +#define glMultiTexCoord3sv glad_glMultiTexCoord3sv +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC)(GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI PFNGLMULTITEXCOORD4DPROC glad_glMultiTexCoord4d; +#define glMultiTexCoord4d glad_glMultiTexCoord4d +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC)(GLenum target, const GLdouble *v); +GLAPI PFNGLMULTITEXCOORD4DVPROC glad_glMultiTexCoord4dv; +#define glMultiTexCoord4dv glad_glMultiTexCoord4dv +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC)(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI PFNGLMULTITEXCOORD4FPROC glad_glMultiTexCoord4f; +#define glMultiTexCoord4f glad_glMultiTexCoord4f +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC)(GLenum target, const GLfloat *v); +GLAPI PFNGLMULTITEXCOORD4FVPROC glad_glMultiTexCoord4fv; +#define glMultiTexCoord4fv glad_glMultiTexCoord4fv +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC)(GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI PFNGLMULTITEXCOORD4IPROC glad_glMultiTexCoord4i; +#define glMultiTexCoord4i glad_glMultiTexCoord4i +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC)(GLenum target, const GLint *v); +GLAPI PFNGLMULTITEXCOORD4IVPROC glad_glMultiTexCoord4iv; +#define glMultiTexCoord4iv glad_glMultiTexCoord4iv +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC)(GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI PFNGLMULTITEXCOORD4SPROC glad_glMultiTexCoord4s; +#define glMultiTexCoord4s glad_glMultiTexCoord4s +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC)(GLenum target, const GLshort *v); +GLAPI PFNGLMULTITEXCOORD4SVPROC glad_glMultiTexCoord4sv; +#define glMultiTexCoord4sv glad_glMultiTexCoord4sv +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC)(const GLfloat *m); +GLAPI PFNGLLOADTRANSPOSEMATRIXFPROC glad_glLoadTransposeMatrixf; +#define glLoadTransposeMatrixf glad_glLoadTransposeMatrixf +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC)(const GLdouble *m); +GLAPI PFNGLLOADTRANSPOSEMATRIXDPROC glad_glLoadTransposeMatrixd; +#define glLoadTransposeMatrixd glad_glLoadTransposeMatrixd +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC)(const GLfloat *m); +GLAPI PFNGLMULTTRANSPOSEMATRIXFPROC glad_glMultTransposeMatrixf; +#define glMultTransposeMatrixf glad_glMultTransposeMatrixf +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC)(const GLdouble *m); +GLAPI PFNGLMULTTRANSPOSEMATRIXDPROC glad_glMultTransposeMatrixd; +#define glMultTransposeMatrixd glad_glMultTransposeMatrixd +#endif +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +GLAPI int GLAD_GL_VERSION_1_4; +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate; +#define glBlendFuncSeparate glad_glBlendFuncSeparate +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC)(GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +GLAPI PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays; +#define glMultiDrawArrays glad_glMultiDrawArrays +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC)(GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +GLAPI PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements; +#define glMultiDrawElements glad_glMultiDrawElements +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC)(GLenum pname, GLfloat param); +GLAPI PFNGLPOINTPARAMETERFPROC glad_glPointParameterf; +#define glPointParameterf glad_glPointParameterf +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC)(GLenum pname, const GLfloat *params); +GLAPI PFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv; +#define glPointParameterfv glad_glPointParameterfv +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC)(GLenum pname, GLint param); +GLAPI PFNGLPOINTPARAMETERIPROC glad_glPointParameteri; +#define glPointParameteri glad_glPointParameteri +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC)(GLenum pname, const GLint *params); +GLAPI PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv; +#define glPointParameteriv glad_glPointParameteriv +typedef void (APIENTRYP PFNGLFOGCOORDFPROC)(GLfloat coord); +GLAPI PFNGLFOGCOORDFPROC glad_glFogCoordf; +#define glFogCoordf glad_glFogCoordf +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC)(const GLfloat *coord); +GLAPI PFNGLFOGCOORDFVPROC glad_glFogCoordfv; +#define glFogCoordfv glad_glFogCoordfv +typedef void (APIENTRYP PFNGLFOGCOORDDPROC)(GLdouble coord); +GLAPI PFNGLFOGCOORDDPROC glad_glFogCoordd; +#define glFogCoordd glad_glFogCoordd +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC)(const GLdouble *coord); +GLAPI PFNGLFOGCOORDDVPROC glad_glFogCoorddv; +#define glFogCoorddv glad_glFogCoorddv +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC)(GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLFOGCOORDPOINTERPROC glad_glFogCoordPointer; +#define glFogCoordPointer glad_glFogCoordPointer +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC)(GLbyte red, GLbyte green, GLbyte blue); +GLAPI PFNGLSECONDARYCOLOR3BPROC glad_glSecondaryColor3b; +#define glSecondaryColor3b glad_glSecondaryColor3b +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC)(const GLbyte *v); +GLAPI PFNGLSECONDARYCOLOR3BVPROC glad_glSecondaryColor3bv; +#define glSecondaryColor3bv glad_glSecondaryColor3bv +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC)(GLdouble red, GLdouble green, GLdouble blue); +GLAPI PFNGLSECONDARYCOLOR3DPROC glad_glSecondaryColor3d; +#define glSecondaryColor3d glad_glSecondaryColor3d +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC)(const GLdouble *v); +GLAPI PFNGLSECONDARYCOLOR3DVPROC glad_glSecondaryColor3dv; +#define glSecondaryColor3dv glad_glSecondaryColor3dv +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC)(GLfloat red, GLfloat green, GLfloat blue); +GLAPI PFNGLSECONDARYCOLOR3FPROC glad_glSecondaryColor3f; +#define glSecondaryColor3f glad_glSecondaryColor3f +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC)(const GLfloat *v); +GLAPI PFNGLSECONDARYCOLOR3FVPROC glad_glSecondaryColor3fv; +#define glSecondaryColor3fv glad_glSecondaryColor3fv +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC)(GLint red, GLint green, GLint blue); +GLAPI PFNGLSECONDARYCOLOR3IPROC glad_glSecondaryColor3i; +#define glSecondaryColor3i glad_glSecondaryColor3i +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC)(const GLint *v); +GLAPI PFNGLSECONDARYCOLOR3IVPROC glad_glSecondaryColor3iv; +#define glSecondaryColor3iv glad_glSecondaryColor3iv +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC)(GLshort red, GLshort green, GLshort blue); +GLAPI PFNGLSECONDARYCOLOR3SPROC glad_glSecondaryColor3s; +#define glSecondaryColor3s glad_glSecondaryColor3s +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC)(const GLshort *v); +GLAPI PFNGLSECONDARYCOLOR3SVPROC glad_glSecondaryColor3sv; +#define glSecondaryColor3sv glad_glSecondaryColor3sv +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC)(GLubyte red, GLubyte green, GLubyte blue); +GLAPI PFNGLSECONDARYCOLOR3UBPROC glad_glSecondaryColor3ub; +#define glSecondaryColor3ub glad_glSecondaryColor3ub +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC)(const GLubyte *v); +GLAPI PFNGLSECONDARYCOLOR3UBVPROC glad_glSecondaryColor3ubv; +#define glSecondaryColor3ubv glad_glSecondaryColor3ubv +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC)(GLuint red, GLuint green, GLuint blue); +GLAPI PFNGLSECONDARYCOLOR3UIPROC glad_glSecondaryColor3ui; +#define glSecondaryColor3ui glad_glSecondaryColor3ui +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC)(const GLuint *v); +GLAPI PFNGLSECONDARYCOLOR3UIVPROC glad_glSecondaryColor3uiv; +#define glSecondaryColor3uiv glad_glSecondaryColor3uiv +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC)(GLushort red, GLushort green, GLushort blue); +GLAPI PFNGLSECONDARYCOLOR3USPROC glad_glSecondaryColor3us; +#define glSecondaryColor3us glad_glSecondaryColor3us +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC)(const GLushort *v); +GLAPI PFNGLSECONDARYCOLOR3USVPROC glad_glSecondaryColor3usv; +#define glSecondaryColor3usv glad_glSecondaryColor3usv +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLSECONDARYCOLORPOINTERPROC glad_glSecondaryColorPointer; +#define glSecondaryColorPointer glad_glSecondaryColorPointer +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC)(GLdouble x, GLdouble y); +GLAPI PFNGLWINDOWPOS2DPROC glad_glWindowPos2d; +#define glWindowPos2d glad_glWindowPos2d +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC)(const GLdouble *v); +GLAPI PFNGLWINDOWPOS2DVPROC glad_glWindowPos2dv; +#define glWindowPos2dv glad_glWindowPos2dv +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC)(GLfloat x, GLfloat y); +GLAPI PFNGLWINDOWPOS2FPROC glad_glWindowPos2f; +#define glWindowPos2f glad_glWindowPos2f +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC)(const GLfloat *v); +GLAPI PFNGLWINDOWPOS2FVPROC glad_glWindowPos2fv; +#define glWindowPos2fv glad_glWindowPos2fv +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC)(GLint x, GLint y); +GLAPI PFNGLWINDOWPOS2IPROC glad_glWindowPos2i; +#define glWindowPos2i glad_glWindowPos2i +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC)(const GLint *v); +GLAPI PFNGLWINDOWPOS2IVPROC glad_glWindowPos2iv; +#define glWindowPos2iv glad_glWindowPos2iv +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC)(GLshort x, GLshort y); +GLAPI PFNGLWINDOWPOS2SPROC glad_glWindowPos2s; +#define glWindowPos2s glad_glWindowPos2s +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC)(const GLshort *v); +GLAPI PFNGLWINDOWPOS2SVPROC glad_glWindowPos2sv; +#define glWindowPos2sv glad_glWindowPos2sv +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC)(GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLWINDOWPOS3DPROC glad_glWindowPos3d; +#define glWindowPos3d glad_glWindowPos3d +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC)(const GLdouble *v); +GLAPI PFNGLWINDOWPOS3DVPROC glad_glWindowPos3dv; +#define glWindowPos3dv glad_glWindowPos3dv +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC)(GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLWINDOWPOS3FPROC glad_glWindowPos3f; +#define glWindowPos3f glad_glWindowPos3f +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC)(const GLfloat *v); +GLAPI PFNGLWINDOWPOS3FVPROC glad_glWindowPos3fv; +#define glWindowPos3fv glad_glWindowPos3fv +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC)(GLint x, GLint y, GLint z); +GLAPI PFNGLWINDOWPOS3IPROC glad_glWindowPos3i; +#define glWindowPos3i glad_glWindowPos3i +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC)(const GLint *v); +GLAPI PFNGLWINDOWPOS3IVPROC glad_glWindowPos3iv; +#define glWindowPos3iv glad_glWindowPos3iv +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC)(GLshort x, GLshort y, GLshort z); +GLAPI PFNGLWINDOWPOS3SPROC glad_glWindowPos3s; +#define glWindowPos3s glad_glWindowPos3s +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC)(const GLshort *v); +GLAPI PFNGLWINDOWPOS3SVPROC glad_glWindowPos3sv; +#define glWindowPos3sv glad_glWindowPos3sv +typedef void (APIENTRYP PFNGLBLENDCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI PFNGLBLENDCOLORPROC glad_glBlendColor; +#define glBlendColor glad_glBlendColor +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC)(GLenum mode); +GLAPI PFNGLBLENDEQUATIONPROC glad_glBlendEquation; +#define glBlendEquation glad_glBlendEquation +#endif +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +GLAPI int GLAD_GL_VERSION_1_5; +typedef void (APIENTRYP PFNGLGENQUERIESPROC)(GLsizei n, GLuint *ids); +GLAPI PFNGLGENQUERIESPROC glad_glGenQueries; +#define glGenQueries glad_glGenQueries +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC)(GLsizei n, const GLuint *ids); +GLAPI PFNGLDELETEQUERIESPROC glad_glDeleteQueries; +#define glDeleteQueries glad_glDeleteQueries +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC)(GLuint id); +GLAPI PFNGLISQUERYPROC glad_glIsQuery; +#define glIsQuery glad_glIsQuery +typedef void (APIENTRYP PFNGLBEGINQUERYPROC)(GLenum target, GLuint id); +GLAPI PFNGLBEGINQUERYPROC glad_glBeginQuery; +#define glBeginQuery glad_glBeginQuery +typedef void (APIENTRYP PFNGLENDQUERYPROC)(GLenum target); +GLAPI PFNGLENDQUERYPROC glad_glEndQuery; +#define glEndQuery glad_glEndQuery +typedef void (APIENTRYP PFNGLGETQUERYIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETQUERYIVPROC glad_glGetQueryiv; +#define glGetQueryiv glad_glGetQueryiv +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC)(GLuint id, GLenum pname, GLint *params); +GLAPI PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv; +#define glGetQueryObjectiv glad_glGetQueryObjectiv +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC)(GLuint id, GLenum pname, GLuint *params); +GLAPI PFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv; +#define glGetQueryObjectuiv glad_glGetQueryObjectuiv +typedef void (APIENTRYP PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); +GLAPI PFNGLBINDBUFFERPROC glad_glBindBuffer; +#define glBindBuffer glad_glBindBuffer +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint *buffers); +GLAPI PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers; +#define glDeleteBuffers glad_glDeleteBuffers +typedef void (APIENTRYP PFNGLGENBUFFERSPROC)(GLsizei n, GLuint *buffers); +GLAPI PFNGLGENBUFFERSPROC glad_glGenBuffers; +#define glGenBuffers glad_glGenBuffers +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC)(GLuint buffer); +GLAPI PFNGLISBUFFERPROC glad_glIsBuffer; +#define glIsBuffer glad_glIsBuffer +typedef void (APIENTRYP PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GLAPI PFNGLBUFFERDATAPROC glad_glBufferData; +#define glBufferData glad_glBufferData +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI PFNGLBUFFERSUBDATAPROC glad_glBufferSubData; +#define glBufferSubData glad_glBufferSubData +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, void *data); +GLAPI PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData; +#define glGetBufferSubData glad_glGetBufferSubData +typedef void * (APIENTRYP PFNGLMAPBUFFERPROC)(GLenum target, GLenum access); +GLAPI PFNGLMAPBUFFERPROC glad_glMapBuffer; +#define glMapBuffer glad_glMapBuffer +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC)(GLenum target); +GLAPI PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer; +#define glUnmapBuffer glad_glUnmapBuffer +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv; +#define glGetBufferParameteriv glad_glGetBufferParameteriv +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC)(GLenum target, GLenum pname, void **params); +GLAPI PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv; +#define glGetBufferPointerv glad_glGetBufferPointerv +#endif +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +GLAPI int GLAD_GL_VERSION_2_0; +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC)(GLenum modeRGB, GLenum modeAlpha); +GLAPI PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate; +#define glBlendEquationSeparate glad_glBlendEquationSeparate +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC)(GLsizei n, const GLenum *bufs); +GLAPI PFNGLDRAWBUFFERSPROC glad_glDrawBuffers; +#define glDrawBuffers glad_glDrawBuffers +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC)(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate; +#define glStencilOpSeparate glad_glStencilOpSeparate +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC)(GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate; +#define glStencilFuncSeparate glad_glStencilFuncSeparate +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC)(GLenum face, GLuint mask); +GLAPI PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate; +#define glStencilMaskSeparate glad_glStencilMaskSeparate +typedef void (APIENTRYP PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader); +GLAPI PFNGLATTACHSHADERPROC glad_glAttachShader; +#define glAttachShader glad_glAttachShader +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC)(GLuint program, GLuint index, const GLchar *name); +GLAPI PFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation; +#define glBindAttribLocation glad_glBindAttribLocation +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader); +GLAPI PFNGLCOMPILESHADERPROC glad_glCompileShader; +#define glCompileShader glad_glCompileShader +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC)(void); +GLAPI PFNGLCREATEPROGRAMPROC glad_glCreateProgram; +#define glCreateProgram glad_glCreateProgram +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC)(GLenum type); +GLAPI PFNGLCREATESHADERPROC glad_glCreateShader; +#define glCreateShader glad_glCreateShader +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC)(GLuint program); +GLAPI PFNGLDELETEPROGRAMPROC glad_glDeleteProgram; +#define glDeleteProgram glad_glDeleteProgram +typedef void (APIENTRYP PFNGLDELETESHADERPROC)(GLuint shader); +GLAPI PFNGLDELETESHADERPROC glad_glDeleteShader; +#define glDeleteShader glad_glDeleteShader +typedef void (APIENTRYP PFNGLDETACHSHADERPROC)(GLuint program, GLuint shader); +GLAPI PFNGLDETACHSHADERPROC glad_glDetachShader; +#define glDetachShader glad_glDetachShader +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); +GLAPI PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray; +#define glDisableVertexAttribArray glad_glDisableVertexAttribArray +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); +GLAPI PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray; +#define glEnableVertexAttribArray glad_glEnableVertexAttribArray +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib; +#define glGetActiveAttrib glad_glGetActiveAttrib +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI PFNGLGETACTIVEUNIFORMPROC glad_glGetActiveUniform; +#define glGetActiveUniform glad_glGetActiveUniform +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC)(GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +GLAPI PFNGLGETATTACHEDSHADERSPROC glad_glGetAttachedShaders; +#define glGetAttachedShaders glad_glGetAttachedShaders +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC)(GLuint program, const GLchar *name); +GLAPI PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation; +#define glGetAttribLocation glad_glGetAttribLocation +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint *params); +GLAPI PFNGLGETPROGRAMIVPROC glad_glGetProgramiv; +#define glGetProgramiv glad_glGetProgramiv +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog; +#define glGetProgramInfoLog glad_glGetProgramInfoLog +typedef void (APIENTRYP PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint *params); +GLAPI PFNGLGETSHADERIVPROC glad_glGetShaderiv; +#define glGetShaderiv glad_glGetShaderiv +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog; +#define glGetShaderInfoLog glad_glGetShaderInfoLog +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GLAPI PFNGLGETSHADERSOURCEPROC glad_glGetShaderSource; +#define glGetShaderSource glad_glGetShaderSource +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar *name); +GLAPI PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation; +#define glGetUniformLocation glad_glGetUniformLocation +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC)(GLuint program, GLint location, GLfloat *params); +GLAPI PFNGLGETUNIFORMFVPROC glad_glGetUniformfv; +#define glGetUniformfv glad_glGetUniformfv +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC)(GLuint program, GLint location, GLint *params); +GLAPI PFNGLGETUNIFORMIVPROC glad_glGetUniformiv; +#define glGetUniformiv glad_glGetUniformiv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC)(GLuint index, GLenum pname, GLdouble *params); +GLAPI PFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv; +#define glGetVertexAttribdv glad_glGetVertexAttribdv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC)(GLuint index, GLenum pname, GLfloat *params); +GLAPI PFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv; +#define glGetVertexAttribfv glad_glGetVertexAttribfv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC)(GLuint index, GLenum pname, GLint *params); +GLAPI PFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv; +#define glGetVertexAttribiv glad_glGetVertexAttribiv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC)(GLuint index, GLenum pname, void **pointer); +GLAPI PFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv; +#define glGetVertexAttribPointerv glad_glGetVertexAttribPointerv +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC)(GLuint program); +GLAPI PFNGLISPROGRAMPROC glad_glIsProgram; +#define glIsProgram glad_glIsProgram +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC)(GLuint shader); +GLAPI PFNGLISSHADERPROC glad_glIsShader; +#define glIsShader glad_glIsShader +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC)(GLuint program); +GLAPI PFNGLLINKPROGRAMPROC glad_glLinkProgram; +#define glLinkProgram glad_glLinkProgram +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GLAPI PFNGLSHADERSOURCEPROC glad_glShaderSource; +#define glShaderSource glad_glShaderSource +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC)(GLuint program); +GLAPI PFNGLUSEPROGRAMPROC glad_glUseProgram; +#define glUseProgram glad_glUseProgram +typedef void (APIENTRYP PFNGLUNIFORM1FPROC)(GLint location, GLfloat v0); +GLAPI PFNGLUNIFORM1FPROC glad_glUniform1f; +#define glUniform1f glad_glUniform1f +typedef void (APIENTRYP PFNGLUNIFORM2FPROC)(GLint location, GLfloat v0, GLfloat v1); +GLAPI PFNGLUNIFORM2FPROC glad_glUniform2f; +#define glUniform2f glad_glUniform2f +typedef void (APIENTRYP PFNGLUNIFORM3FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI PFNGLUNIFORM3FPROC glad_glUniform3f; +#define glUniform3f glad_glUniform3f +typedef void (APIENTRYP PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI PFNGLUNIFORM4FPROC glad_glUniform4f; +#define glUniform4f glad_glUniform4f +typedef void (APIENTRYP PFNGLUNIFORM1IPROC)(GLint location, GLint v0); +GLAPI PFNGLUNIFORM1IPROC glad_glUniform1i; +#define glUniform1i glad_glUniform1i +typedef void (APIENTRYP PFNGLUNIFORM2IPROC)(GLint location, GLint v0, GLint v1); +GLAPI PFNGLUNIFORM2IPROC glad_glUniform2i; +#define glUniform2i glad_glUniform2i +typedef void (APIENTRYP PFNGLUNIFORM3IPROC)(GLint location, GLint v0, GLint v1, GLint v2); +GLAPI PFNGLUNIFORM3IPROC glad_glUniform3i; +#define glUniform3i glad_glUniform3i +typedef void (APIENTRYP PFNGLUNIFORM4IPROC)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI PFNGLUNIFORM4IPROC glad_glUniform4i; +#define glUniform4i glad_glUniform4i +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM1FVPROC glad_glUniform1fv; +#define glUniform1fv glad_glUniform1fv +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM2FVPROC glad_glUniform2fv; +#define glUniform2fv glad_glUniform2fv +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM3FVPROC glad_glUniform3fv; +#define glUniform3fv glad_glUniform3fv +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM4FVPROC glad_glUniform4fv; +#define glUniform4fv glad_glUniform4fv +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM1IVPROC glad_glUniform1iv; +#define glUniform1iv glad_glUniform1iv +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM2IVPROC glad_glUniform2iv; +#define glUniform2iv glad_glUniform2iv +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM3IVPROC glad_glUniform3iv; +#define glUniform3iv glad_glUniform3iv +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM4IVPROC glad_glUniform4iv; +#define glUniform4iv glad_glUniform4iv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX2FVPROC glad_glUniformMatrix2fv; +#define glUniformMatrix2fv glad_glUniformMatrix2fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX3FVPROC glad_glUniformMatrix3fv; +#define glUniformMatrix3fv glad_glUniformMatrix3fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv; +#define glUniformMatrix4fv glad_glUniformMatrix4fv +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC)(GLuint program); +GLAPI PFNGLVALIDATEPROGRAMPROC glad_glValidateProgram; +#define glValidateProgram glad_glValidateProgram +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC)(GLuint index, GLdouble x); +GLAPI PFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d; +#define glVertexAttrib1d glad_glVertexAttrib1d +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv; +#define glVertexAttrib1dv glad_glVertexAttrib1dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC)(GLuint index, GLfloat x); +GLAPI PFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f; +#define glVertexAttrib1f glad_glVertexAttrib1f +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB1FVPROC glad_glVertexAttrib1fv; +#define glVertexAttrib1fv glad_glVertexAttrib1fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC)(GLuint index, GLshort x); +GLAPI PFNGLVERTEXATTRIB1SPROC glad_glVertexAttrib1s; +#define glVertexAttrib1s glad_glVertexAttrib1s +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB1SVPROC glad_glVertexAttrib1sv; +#define glVertexAttrib1sv glad_glVertexAttrib1sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC)(GLuint index, GLdouble x, GLdouble y); +GLAPI PFNGLVERTEXATTRIB2DPROC glad_glVertexAttrib2d; +#define glVertexAttrib2d glad_glVertexAttrib2d +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB2DVPROC glad_glVertexAttrib2dv; +#define glVertexAttrib2dv glad_glVertexAttrib2dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC)(GLuint index, GLfloat x, GLfloat y); +GLAPI PFNGLVERTEXATTRIB2FPROC glad_glVertexAttrib2f; +#define glVertexAttrib2f glad_glVertexAttrib2f +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB2FVPROC glad_glVertexAttrib2fv; +#define glVertexAttrib2fv glad_glVertexAttrib2fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC)(GLuint index, GLshort x, GLshort y); +GLAPI PFNGLVERTEXATTRIB2SPROC glad_glVertexAttrib2s; +#define glVertexAttrib2s glad_glVertexAttrib2s +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB2SVPROC glad_glVertexAttrib2sv; +#define glVertexAttrib2sv glad_glVertexAttrib2sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLVERTEXATTRIB3DPROC glad_glVertexAttrib3d; +#define glVertexAttrib3d glad_glVertexAttrib3d +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB3DVPROC glad_glVertexAttrib3dv; +#define glVertexAttrib3dv glad_glVertexAttrib3dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLVERTEXATTRIB3FPROC glad_glVertexAttrib3f; +#define glVertexAttrib3f glad_glVertexAttrib3f +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB3FVPROC glad_glVertexAttrib3fv; +#define glVertexAttrib3fv glad_glVertexAttrib3fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC)(GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI PFNGLVERTEXATTRIB3SPROC glad_glVertexAttrib3s; +#define glVertexAttrib3s glad_glVertexAttrib3s +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB3SVPROC glad_glVertexAttrib3sv; +#define glVertexAttrib3sv glad_glVertexAttrib3sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC)(GLuint index, const GLbyte *v); +GLAPI PFNGLVERTEXATTRIB4NBVPROC glad_glVertexAttrib4Nbv; +#define glVertexAttrib4Nbv glad_glVertexAttrib4Nbv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIB4NIVPROC glad_glVertexAttrib4Niv; +#define glVertexAttrib4Niv glad_glVertexAttrib4Niv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB4NSVPROC glad_glVertexAttrib4Nsv; +#define glVertexAttrib4Nsv glad_glVertexAttrib4Nsv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI PFNGLVERTEXATTRIB4NUBPROC glad_glVertexAttrib4Nub; +#define glVertexAttrib4Nub glad_glVertexAttrib4Nub +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC)(GLuint index, const GLubyte *v); +GLAPI PFNGLVERTEXATTRIB4NUBVPROC glad_glVertexAttrib4Nubv; +#define glVertexAttrib4Nubv glad_glVertexAttrib4Nubv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIB4NUIVPROC glad_glVertexAttrib4Nuiv; +#define glVertexAttrib4Nuiv glad_glVertexAttrib4Nuiv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC)(GLuint index, const GLushort *v); +GLAPI PFNGLVERTEXATTRIB4NUSVPROC glad_glVertexAttrib4Nusv; +#define glVertexAttrib4Nusv glad_glVertexAttrib4Nusv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC)(GLuint index, const GLbyte *v); +GLAPI PFNGLVERTEXATTRIB4BVPROC glad_glVertexAttrib4bv; +#define glVertexAttrib4bv glad_glVertexAttrib4bv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI PFNGLVERTEXATTRIB4DPROC glad_glVertexAttrib4d; +#define glVertexAttrib4d glad_glVertexAttrib4d +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB4DVPROC glad_glVertexAttrib4dv; +#define glVertexAttrib4dv glad_glVertexAttrib4dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI PFNGLVERTEXATTRIB4FPROC glad_glVertexAttrib4f; +#define glVertexAttrib4f glad_glVertexAttrib4f +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB4FVPROC glad_glVertexAttrib4fv; +#define glVertexAttrib4fv glad_glVertexAttrib4fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIB4IVPROC glad_glVertexAttrib4iv; +#define glVertexAttrib4iv glad_glVertexAttrib4iv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC)(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI PFNGLVERTEXATTRIB4SPROC glad_glVertexAttrib4s; +#define glVertexAttrib4s glad_glVertexAttrib4s +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv; +#define glVertexAttrib4sv glad_glVertexAttrib4sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC)(GLuint index, const GLubyte *v); +GLAPI PFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv; +#define glVertexAttrib4ubv glad_glVertexAttrib4ubv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv; +#define glVertexAttrib4uiv glad_glVertexAttrib4uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC)(GLuint index, const GLushort *v); +GLAPI PFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv; +#define glVertexAttrib4usv glad_glVertexAttrib4usv +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +GLAPI PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer; +#define glVertexAttribPointer glad_glVertexAttribPointer +#endif +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 +GLAPI int GLAD_GL_VERSION_2_1; +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX2X3FVPROC glad_glUniformMatrix2x3fv; +#define glUniformMatrix2x3fv glad_glUniformMatrix2x3fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX3X2FVPROC glad_glUniformMatrix3x2fv; +#define glUniformMatrix3x2fv glad_glUniformMatrix3x2fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX2X4FVPROC glad_glUniformMatrix2x4fv; +#define glUniformMatrix2x4fv glad_glUniformMatrix2x4fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX4X2FVPROC glad_glUniformMatrix4x2fv; +#define glUniformMatrix4x2fv glad_glUniformMatrix4x2fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX3X4FVPROC glad_glUniformMatrix3x4fv; +#define glUniformMatrix3x4fv glad_glUniformMatrix3x4fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv; +#define glUniformMatrix4x3fv glad_glUniformMatrix4x3fv +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/shf/ffi/gles3-import.sml b/shf/ffi/gles3-import.sml new file mode 100644 index 0000000..357977a --- /dev/null +++ b/shf/ffi/gles3-import.sml @@ -0,0 +1,63 @@ +structure Gles3 = +struct + type buffer = Word32.word + type shader_type = Word32.word + type shader = Word32.word + type program = Word32.word + type draw_mode = Word32.word + type update_mode = Word32.word + + (* OpenGL constants used. *) + val (VERTEX_SHADER, _) = + _symbol "VERTEX_SHADER" public : ( unit -> shader_type ) * ( shader_type -> unit ); + val VERTEX_SHADER = VERTEX_SHADER () + + val (FRAGMENT_SHADER, _) = + _symbol "FRAGMENT_SHADER" public : ( unit -> shader_type ) * ( shader_type -> unit ); + val FRAGMENT_SHADER = FRAGMENT_SHADER () + + val (TRIANGLES, _) = + _symbol "TRIANGLES" public : ( unit -> draw_mode ) * ( draw_mode -> unit ); + val TRIANGLES = TRIANGLES () + + val (STATIC_DRAW, _) = + _symbol "STATIC_DRAW" public : ( unit -> update_mode ) * ( update_mode -> unit ); + val STATIC_DRAW = STATIC_DRAW () + + val (DYNAMIC_DRAW, _) = + _symbol "DYNAMIC_DRAW" public : ( unit -> update_mode ) * ( update_mode -> unit ); + val DYNAMIC_DRAW = DYNAMIC_DRAW () + + (* OpenGL functions used. *) + val viewport = _import "viewport" public : int * int -> unit; + val enableDepthTest = _import "enableDepthTest" : unit -> unit; + + val createBuffer = _import "createBuffer" public : unit -> buffer; + val bindBuffer = _import "bindBuffer" public : buffer -> unit; + val bufferData = + _import "bufferData" public : Real32.real vector * int * update_mode -> unit; + + val createShader = _import "createShader" public : shader_type -> shader; + val compileShader = _import "compileShader" public : shader -> unit; + val deleteShader = _import "deleteShader" public : shader -> unit; + val shaderSource = _import "shaderSource" public : shader * string -> unit; + + val vertexAttribPointer = + _import "vertexAttribPointer" public : int * int * int * int -> unit; + val enableVertexAttribArray = + _import "enableVertexAttribArray" public : int -> unit; + + val createProgram = _import "createProgram" public : unit -> program; + val attachShader = _import "attachShader" public : program * shader -> unit; + val linkProgram = _import "linkProgram" public : program -> unit; + val useProgram = _import "useProgram" public : program -> unit; + + val deleteShader = _import "deleteShader" public : program -> unit; + val deleteProgram = _import "deleteProgram" public : program -> unit; + + val clearColor = + _import "clearColor" public : Real32.real * Real32.real * Real32.real * Real32.real -> unit; + val clear = _import "clear" public : unit -> unit; + + val drawArrays = _import "drawArrays" public : draw_mode * int * int -> unit; +end diff --git a/shf/ffi/glfw-export.c b/shf/ffi/glfw-export.c new file mode 100644 index 0000000..f87b84b --- /dev/null +++ b/shf/ffi/glfw-export.c @@ -0,0 +1,144 @@ +#define GLFW_INCLUDE_NONE +#include "glad.h" +#include +#include + +// GLFW constants used below +int CONTEXT_VERSION_MAJOR = GLFW_CONTEXT_VERSION_MAJOR; +int DEPRECATED = GLFW_DECORATED; +int GLFW_FFI_FALSE = GLFW_FALSE; + +// GLFW functions used below +void init() { + glfwInit(); +} + +void windowHint(int hint, int value) { + glfwWindowHint(hint, value); +} + +GLFWwindow* createWindow(int width, int height, const char *title) { + return glfwCreateWindow(width, height, title, NULL, NULL); +} + +void terminate() { + glfwTerminate(); +} + +void makeContextCurrent(GLFWwindow* window) { + glfwMakeContextCurrent(window); +} + +bool windowShouldClose(GLFWwindow *window) { + glfwWindowShouldClose(window); +} + +void waitEvents() { + glfwWaitEvents(); +} + +void swapBuffers(GLFWwindow *window) { + glfwSwapBuffers(window); +} + +void setClipboardString (GLFWwindow *window, const char *copyString) { + glfwSetClipboardString(window, copyString); +} + +void loadGlad() { + gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); +} + +// OpenGL constants used below +unsigned int VERTEX_SHADER = GL_VERTEX_SHADER; +unsigned int FRAGMENT_SHADER = GL_FRAGMENT_SHADER; +unsigned int TRIANGLES = GL_TRIANGLES; +unsigned int STATIC_DRAW = GL_STATIC_DRAW; +unsigned int DYNAMIC_DRAW = GL_DYNAMIC_DRAW; + +// OpenGL functions used below +void enableDepthTest() { + glEnable(GL_DEPTH_TEST); +} + +void viewport(int width, int height) { + glViewport(0, 0, width, height); +} + +void clearColor(float r, float g, float b, float a) { + glClearColor(r, g, b, a); +} + +void clear() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +unsigned int createBuffer() { + unsigned int buffer; + glGenBuffers(1, &buffer); + return buffer; +} + +void bindBuffer(unsigned int buffer) { + glBindBuffer(GL_ARRAY_BUFFER, buffer); +} + +void bufferData(float* vector, int vectorLength, unsigned int updateMode) { + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vectorLength, vector, updateMode); +} + +unsigned int createShader(unsigned int shaderType) { + return glCreateShader(shaderType); +} + +void shaderSource(unsigned int shader, const char *sourceString) { + glShaderSource(shader, 1, &sourceString, NULL); +} + +void compileShader(unsigned int shader) { + glCompileShader(shader); +} + +void deleteShader(unsigned int shader) { + glDeleteShader(shader); +} + +void vertexAttribPointer(int location, int numVecComponents, int stride, int offset) { + glVertexAttribPointer(location, numVecComponents, GL_FLOAT, GL_FALSE, stride * sizeof(float), (void*)offset); +} + +void enableVertexAttribArray(int location) { + glEnableVertexAttribArray(location); +} + +unsigned int createProgram() { + return glCreateProgram(); +} + +void attachShader(unsigned int program, unsigned int shader) { + glAttachShader(program, shader); +} + +void linkProgram(unsigned int program) { + glLinkProgram(program); +} + +void useProgram(unsigned int program) { + glUseProgram(program); +} + +void deleteProgram(unsigned int program) { + glDeleteProgram(program); +} + +void drawArrays(unsigned int drawMode, int startIndex, int numVertices) { + glDrawArrays(drawMode, startIndex, numVertices); +} + +int getUniformLocation(unsigned int program, const char *uniformName) { + glGetUniformLocation(program, uniformName); +} + +void uniform4f(int uniformLocation, float a, float b, float c, float d) { + glUniform4f(uniformLocation, a, b, c, d); +} diff --git a/shf/ffi/glfw-import.sml b/shf/ffi/glfw-import.sml new file mode 100644 index 0000000..b8ff24a --- /dev/null +++ b/shf/ffi/glfw-import.sml @@ -0,0 +1,25 @@ +structure Glfw = +struct + type window = MLton.Pointer.t + + (* Window hint constants. *) + val (CONTEXT_VERSION_MAJOR, _) = + _symbol "CONTEXT_VERSION_MAJOR" public : ( unit -> int ) * ( int -> unit ); + val (DEPRECATED, _) = + _symbol "DEPRECATED" public : ( unit -> int ) * ( int -> unit ); + val (FALSE, _) = + _symbol "GLFW_FFI_FALSE" public : ( unit -> int ) * ( int -> unit ); + + (* GLFW functions. *) + val init = _import "init" public : unit -> unit; + val windowHint = _import "windowHint" public : int * int -> unit; + val createWindow = + _import "createWindow" public : int * int * string -> window; + val terminate = _import "terminate" public : unit -> unit; + val makeContextCurrent = _import "makeContextCurrent" public : window -> unit; + val windowShouldClose = _import "windowShouldClose" public : window -> bool; + val waitEvents = _import "waitEvents" public reentrant : unit -> unit; + val swapBuffers = _import "swapBuffers" public : window -> unit; + val setClipboardString = _import "setClipboardString" public : window * string -> unit; + val loadGlad = _import "loadGlad" public : unit -> unit; +end diff --git a/shf/ffi/glfw-input.c b/shf/ffi/glfw-input.c new file mode 100644 index 0000000..04c5dbb --- /dev/null +++ b/shf/ffi/glfw-input.c @@ -0,0 +1,119 @@ +#include "mlton-glfw-export.h" +#include "glad.h" +#define GLFW_INCLUDE_NONE +#include + +int PRESS = GLFW_PRESS; +int REPEAT = GLFW_REPEAT; +int RELEASE = GLFW_RELEASE; +int KEY_ESC = GLFW_KEY_ESCAPE; +int KEY_ENTER = GLFW_KEY_ENTER; +int KEY_BACKSPACE = GLFW_KEY_BACKSPACE; + +int KEY_ARROW_LEFT = GLFW_KEY_LEFT; +int KEY_ARROW_RIGHT = GLFW_KEY_RIGHT; +int KEY_ARROW_UP = GLFW_KEY_UP; +int KEY_ARROW_DOWN = GLFW_KEY_DOWN; + +void framebufferSizeCallback(GLFWwindow* window, int width, int height) { + glViewport(0, 0, width, height); + mltonFramebufferSizeCallback(width, height); +} + +void setFramebufferSizeCallback(GLFWwindow* window) { + glfwSetFramebufferSizeCallback(window, framebufferSizeCallback); +} + +void charCallback(GLFWwindow* window, unsigned int codepoint) { + mltonCharCallback(codepoint); +} + +void setCharCallback(GLFWwindow* window) { + glfwSetCharCallback(window, charCallback); +} + +void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) { + mltonKeyCallback(key, scancode, action, mods); +} + +void setKeyCallback(GLFWwindow *window) { + glfwSetKeyCallback(window, keyCallback); +} + +// gamepad code +GLFWgamepadstate state; +float* axes; +int axesCount = -1; + +int getGamepadState(int joystickID) { + if (glfwJoystickIsGamepad(joystickID) && glfwGetGamepadState(joystickID, &state)) { + axes = glfwGetJoystickAxes(joystickID, &axesCount); + return 1; + } else { + return 0; + } +} + +float getLeftJoystickXAxisState() { + if (axesCount >= 2) { + return axes[0]; + } else { + return 99.0; + } +} + +float getLeftJoystickYAxisState() { + if (axesCount >= 2) { + return axes[1]; + } else { + return 99.0; + } +} + +float getL2State() { + return axes[2]; +} + +float getR2State() { + return axes[5]; +} + +int isCrossButtonPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_CROSS]; +} + +int isCircleButtonPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_CIRCLE]; +} + +int isSquareButtonPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_SQUARE]; +} + +int isTriangleButtonPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_TRIANGLE]; +} + +int isR1ButtonPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER]; +} + +int isL1ButtonPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER]; +} + +int isDpadUpPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP]; +} + +int isDpadDownPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN]; +} + +int isDpadLeftPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT]; +} + +int isDpadRightPressed() { + return state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT]; +} diff --git a/shf/ffi/glfw-input.sml b/shf/ffi/glfw-input.sml new file mode 100644 index 0000000..17499c5 --- /dev/null +++ b/shf/ffi/glfw-input.sml @@ -0,0 +1,91 @@ +structure Input = +struct + type window = MLton.Pointer.t + + (* Constants. *) + val (PRESS, _) = + _symbol "PRESS" public : ( unit -> int ) * ( int -> unit ); + val PRESS = PRESS () + + val (REPEAT, _) = + _symbol "REPEAT" public : ( unit -> int ) * ( int -> unit ); + val REPEAT = REPEAT () + + val (RELEASE, _) = + _symbol "RELEASE" public : ( unit -> int ) * ( int -> unit ); + val RELEASE = RELEASE () + + val exportFramebufferSizeCallback = + _export "mltonFramebufferSizeCallback" public : (int * int -> unit) -> unit; + val setFramebufferSizeCallback = + _import "setFramebufferSizeCallback" public : window -> unit; + + val exportCharCallback = + _export "mltonCharCallback" public : (Word32.word -> unit) -> unit; + val setCharCallback = + _import "setCharCallback" public : window -> unit; + + val exportKeyCallback = + _export "mltonKeyCallback" public : (int * int * int * int -> unit) -> unit; + val setKeyCallback = + _import "setKeyCallback" public : window -> unit; + + val (KEY_ESC, _) = + _symbol "KEY_ESC" public : ( unit -> int ) * ( int -> unit ); + val KEY_ESC = KEY_ESC () + val (KEY_ENTER, _) = + _symbol "KEY_ENTER" public : ( unit -> int ) * ( int -> unit ); + val KEY_ENTER = KEY_ENTER () + val (KEY_BACKSPACE, _) = + _symbol "KEY_BACKSPACE" public : ( unit -> int ) * ( int -> unit ); + val KEY_BACKSPACE = KEY_BACKSPACE () + + val (KEY_ARROW_LEFT, _) = + _symbol "KEY_ARROW_LEFT" public : ( unit -> int ) * ( int -> unit ); + val KEY_ARROW_LEFT = KEY_ARROW_LEFT () + val (KEY_ARROW_UP, _) = + _symbol "KEY_ARROW_UP" public : ( unit -> int ) * ( int -> unit ); + val KEY_ARROW_UP = KEY_ARROW_UP () + val (KEY_ARROW_RIGHT, _) = + _symbol "KEY_ARROW_RIGHT" public : ( unit -> int ) * ( int -> unit ); + val KEY_ARROW_RIGHT = KEY_ARROW_RIGHT () + val (KEY_ARROW_DOWN, _) = + _symbol "KEY_ARROW_DOWN" public : ( unit -> int ) * ( int -> unit ); + val KEY_ARROW_DOWN = KEY_ARROW_DOWN () + + (* gamepad bindings below *) + val getGamepadState = + _import "getGamepadState" public : int -> int; + + val getLeftJoystickXAxisState = + _import "getLeftJoystickXAxisState" public : unit -> Real32.real; + val getLeftJoystickYAxisState = + _import "getLeftJoystickYAxisState" public : unit -> Real32.real; + + val isCrossButtonPressed = + _import "isCrossButtonPressed" public : unit -> int; + val isCircleButtonPressed = + _import "isCircleButtonPressed" public : unit -> int; + val isSquareButtonPressed = + _import "isSquareButtonPressed" public : unit -> int; + val isTriangleButtonPressed = + _import "isTriangleButtonPressed" public : unit -> int; + val isR1ButtonPressed = + _import "isR1ButtonPressed" public : unit -> int; + val isL1ButtonPressed = + _import "isL1ButtonPressed" public : unit -> int; + + val isDpadUpButtonPressed = + _import "isDpadUpButtonPressed" public : unit -> int; + val isDpadDownButtonPressed = + _import "isDpadDownButtonPressed" public : unit -> int; + val isDpadLeftButtonPressed = + _import "isDpadLeftButtonPressed" public : unit -> int; + val isDpadRightButtonPressed = + _import "isDpadRightButtonPressed" public : unit -> int; + + val getR2State = + _import "getR2State" public : unit -> Real32.real; + val getL2State = + _import "getL2State" public : unit -> Real32.real; +end diff --git a/shf/ffi/khrplatform.h b/shf/ffi/khrplatform.h new file mode 100644 index 0000000..975bbff --- /dev/null +++ b/shf/ffi/khrplatform.h @@ -0,0 +1,282 @@ +#ifndef __khrplatform_h_ +#define __khrplatform_h_ + +/* +** Copyright (c) 2008-2018 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Khronos platform-specific types and definitions. + * + * The master copy of khrplatform.h is maintained in the Khronos EGL + * Registry repository at https://github.com/KhronosGroup/EGL-Registry + * The last semantic modification to khrplatform.h was at commit ID: + * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 + * + * Adopters may modify this file to suit their platform. Adopters are + * encouraged to submit platform specific modifications to the Khronos + * group so that they can be included in future versions of this file. + * Please submit changes by filing pull requests or issues on + * the EGL Registry repository linked above. + * + * + * See the Implementer's Guidelines for information about where this file + * should be located on your system and for more details of its use: + * http://www.khronos.org/registry/implementers_guide.pdf + * + * This file should be included as + * #include + * by Khronos client API header files that use its types and defines. + * + * The types in khrplatform.h should only be used to define API-specific types. + * + * Types defined in khrplatform.h: + * khronos_int8_t signed 8 bit + * khronos_uint8_t unsigned 8 bit + * khronos_int16_t signed 16 bit + * khronos_uint16_t unsigned 16 bit + * khronos_int32_t signed 32 bit + * khronos_uint32_t unsigned 32 bit + * khronos_int64_t signed 64 bit + * khronos_uint64_t unsigned 64 bit + * khronos_intptr_t signed same number of bits as a pointer + * khronos_uintptr_t unsigned same number of bits as a pointer + * khronos_ssize_t signed size + * khronos_usize_t unsigned size + * khronos_float_t signed 32 bit floating point + * khronos_time_ns_t unsigned 64 bit time in nanoseconds + * khronos_utime_nanoseconds_t unsigned time interval or absolute time in + * nanoseconds + * khronos_stime_nanoseconds_t signed time interval in nanoseconds + * khronos_boolean_enum_t enumerated boolean type. This should + * only be used as a base type when a client API's boolean type is + * an enum. Client APIs which use an integer or other type for + * booleans cannot use this as the base type for their boolean. + * + * Tokens defined in khrplatform.h: + * + * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. + * + * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. + * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. + * + * Calling convention macros defined in this file: + * KHRONOS_APICALL + * KHRONOS_APIENTRY + * KHRONOS_APIATTRIBUTES + * + * These may be used in function prototypes as: + * + * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( + * int arg1, + * int arg2) KHRONOS_APIATTRIBUTES; + */ + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APICALL + *------------------------------------------------------------------------- + * This precedes the return type of the function in the function prototype. + */ +#if defined(_WIN32) && !defined(__SCITECH_SNAP__) +# define KHRONOS_APICALL __declspec(dllimport) +#elif defined (__SYMBIAN32__) +# define KHRONOS_APICALL IMPORT_C +#elif defined(__ANDROID__) +# define KHRONOS_APICALL __attribute__((visibility("default"))) +#else +# define KHRONOS_APICALL +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIENTRY + *------------------------------------------------------------------------- + * This follows the return type of the function and precedes the function + * name in the function prototype. + */ +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) + /* Win32 but not WinCE */ +# define KHRONOS_APIENTRY __stdcall +#else +# define KHRONOS_APIENTRY +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIATTRIBUTES + *------------------------------------------------------------------------- + * This follows the closing parenthesis of the function prototype arguments. + */ +#if defined (__ARMCC_2__) +#define KHRONOS_APIATTRIBUTES __softfp +#else +#define KHRONOS_APIATTRIBUTES +#endif + +/*------------------------------------------------------------------------- + * basic type definitions + *-----------------------------------------------------------------------*/ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) + + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__VMS ) || defined(__sgi) + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) + +/* + * Win32 + */ +typedef __int32 khronos_int32_t; +typedef unsigned __int32 khronos_uint32_t; +typedef __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__sun__) || defined(__digital__) + +/* + * Sun or Digital + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#if defined(__arch64__) || defined(_LP64) +typedef long int khronos_int64_t; +typedef unsigned long int khronos_uint64_t; +#else +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#endif /* __arch64__ */ +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif 0 + +/* + * Hypothetical platform with no float or int64 support + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#define KHRONOS_SUPPORT_INT64 0 +#define KHRONOS_SUPPORT_FLOAT 0 + +#else + +/* + * Generic fallback + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#endif + + +/* + * Types that are (so far) the same on all platforms + */ +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; + +/* + * Types that differ between LLP64 and LP64 architectures - in LLP64, + * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears + * to be the only LLP64 architecture in current use. + */ +#ifdef _WIN64 +typedef signed long long int khronos_intptr_t; +typedef unsigned long long int khronos_uintptr_t; +typedef signed long long int khronos_ssize_t; +typedef unsigned long long int khronos_usize_t; +#else +typedef signed long int khronos_intptr_t; +typedef unsigned long int khronos_uintptr_t; +typedef signed long int khronos_ssize_t; +typedef unsigned long int khronos_usize_t; +#endif + +#if KHRONOS_SUPPORT_FLOAT +/* + * Float type + */ +typedef float khronos_float_t; +#endif + +#if KHRONOS_SUPPORT_INT64 +/* Time types + * + * These types can be used to represent a time interval in nanoseconds or + * an absolute Unadjusted System Time. Unadjusted System Time is the number + * of nanoseconds since some arbitrary system event (e.g. since the last + * time the system booted). The Unadjusted System Time is an unsigned + * 64 bit value that wraps back to 0 every 584 years. Time intervals + * may be either signed or unsigned. + */ +typedef khronos_uint64_t khronos_utime_nanoseconds_t; +typedef khronos_int64_t khronos_stime_nanoseconds_t; +#endif + +/* + * Dummy value used to pad enum types to 32 bits. + */ +#ifndef KHRONOS_MAX_ENUM +#define KHRONOS_MAX_ENUM 0x7FFFFFFF +#endif + +/* + * Enumerated boolean type + * + * Values other than zero should be considered to be true. Therefore + * comparisons should not be made against KHRONOS_TRUE. + */ +typedef enum { + KHRONOS_FALSE = 0, + KHRONOS_TRUE = 1, + KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM +} khronos_boolean_enum_t; + +#endif /* __khrplatform_h_ */ diff --git a/shf/ffi/mlton-glfw-export.h b/shf/ffi/mlton-glfw-export.h new file mode 100644 index 0000000..131e883 --- /dev/null +++ b/shf/ffi/mlton-glfw-export.h @@ -0,0 +1,171 @@ +#ifndef __SHF_GLFW_ML_H__ +#define __SHF_GLFW_ML_H__ + +/* Copyright (C) 2004-2007 Henry Cejtin, Matthew Fluet, Suresh + * Jagannathan, and Stephen Weeks. + * + * MLton is released under a HPND-style license. + * See the file MLton-LICENSE for details. + */ + +#ifndef _MLTON_MLTYPES_H_ +#define _MLTON_MLTYPES_H_ + +/* We need these because in header files for exported SML functions, + * types.h is included without cenv.h. + */ +#if (defined (_AIX) || defined (__hpux__) || defined (__OpenBSD__)) +#include +#elif (defined (__sun__)) +#include +#else +#include +#endif + +/* ML types */ +typedef unsigned char PointerAux __attribute__ ((may_alias)); +typedef PointerAux* Pointer; +#define Array(t) Pointer +#define Ref(t) Pointer +#define Vector(t) Pointer + +typedef int8_t Int8_t; +typedef int8_t Int8; +typedef int16_t Int16_t; +typedef int16_t Int16; +typedef int32_t Int32_t; +typedef int32_t Int32; +typedef int64_t Int64_t; +typedef int64_t Int64; +typedef float Real32_t; +typedef float Real32; +typedef double Real64_t; +typedef double Real64; +typedef uint8_t Word8_t; +typedef uint8_t Word8; +typedef uint16_t Word16_t; +typedef uint16_t Word16; +typedef uint32_t Word32_t; +typedef uint32_t Word32; +typedef uint64_t Word64_t; +typedef uint64_t Word64; + +typedef Int8_t WordS8_t; +typedef Int8_t WordS8; +typedef Int16_t WordS16_t; +typedef Int16_t WordS16; +typedef Int32_t WordS32_t; +typedef Int32_t WordS32; +typedef Int64_t WordS64_t; +typedef Int64_t WordS64; + +typedef Word8_t WordU8_t; +typedef Word8_t WordU8; +typedef Word16_t WordU16_t; +typedef Word16_t WordU16; +typedef Word32_t WordU32_t; +typedef Word32_t WordU32; +typedef Word64_t WordU64_t; +typedef Word64_t WordU64; + +typedef WordU8_t Char8_t; +typedef WordU8_t Char8; +typedef WordU16_t Char16_t; +typedef WordU16_t Char16; +typedef WordU32_t Char32_t; +typedef WordU32_t Char32; + +typedef Vector(Char8_t) String8_t; +typedef Vector(Char8_t) String8; +typedef Vector(Char16_t) String16_t; +typedef Vector(Char16_t) String16; +typedef Vector(Char32_t) String32_t; +typedef Vector(Char32_t) String32; + +typedef Int32_t Bool_t; +typedef Int32_t Bool; +typedef String8_t NullString8_t; +typedef String8_t NullString8; + +typedef void* CPointer; +typedef Pointer Objptr; + +#endif /* _MLTON_MLTYPES_H_ */ + +/* Copyright (C) 1999-2007 Henry Cejtin, Matthew Fluet, Suresh + * Jagannathan, and Stephen Weeks. + * Copyright (C) 1997-2000 NEC Research Institute. + * + * MLton is released under a HPND-style license. + * See the file MLton-LICENSE for details. + */ + +#ifndef _MLTON_EXPORT_H_ +#define _MLTON_EXPORT_H_ + +/* ------------------------------------------------- */ +/* Symbols */ +/* ------------------------------------------------- */ + +/* An external symbol is something not defined by the module + * (executable or library) being built. Rather, it is provided + * from a library dependency (dll, dylib, or shared object). + * + * A public symbol is defined in this module as being available + * to users outside of this module. If building a library, this + * means the symbol will be part of the public interface. + * + * A private symbol is defined within this module, but will not + * be made available outside of it. This is typically used for + * internal implementation details that should not be accessible. + */ + +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) +#define EXTERNAL __declspec(dllimport) +#define PUBLIC __declspec(dllexport) +#define PRIVATE +#else +#define EXTERNAL __attribute__((visibility("default"))) +#define PUBLIC __attribute__((visibility("default"))) +#define PRIVATE __attribute__((visibility("hidden"))) +#endif + +#endif /* _MLTON_EXPORT_H_ */ + +#if !defined(PART_OF_SHF_GLFW) && \ + !defined(STATIC_LINK_SHF_GLFW) && \ + !defined(DYNAMIC_LINK_SHF_GLFW) +#define PART_OF_SHF_GLFW +#endif + +#if defined(PART_OF_SHF_GLFW) +#define MLLIB_PRIVATE(x) PRIVATE x +#define MLLIB_PUBLIC(x) PUBLIC x +#elif defined(STATIC_LINK_SHF_GLFW) +#define MLLIB_PRIVATE(x) +#define MLLIB_PUBLIC(x) PUBLIC x +#elif defined(DYNAMIC_LINK_SHF_GLFW) +#define MLLIB_PRIVATE(x) +#define MLLIB_PUBLIC(x) EXTERNAL x +#else +#error Must specify linkage for shf_glfw +#define MLLIB_PRIVATE(x) +#define MLLIB_PUBLIC(x) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +MLLIB_PUBLIC(void mltonFramebufferSizeCallback (Int32 x0, Int32 x1);) +MLLIB_PUBLIC(void mltonCharCallback (Word32 x0);) +MLLIB_PUBLIC(void mltonKeyCallback (Int32 x0, Int32 x1, Int32 x2, Int32 x3);) + +#undef MLLIB_PRIVATE +#undef MLLIB_PUBLIC + +#ifdef __cplusplus +} +#endif + +#endif /* __SHF_GLFW_ML_H__ */ diff --git a/shf/ffi/mlton-rgfw-export.h b/shf/ffi/mlton-rgfw-export.h new file mode 100644 index 0000000..4afae32 --- /dev/null +++ b/shf/ffi/mlton-rgfw-export.h @@ -0,0 +1,172 @@ +#ifndef __SHF_RGFW_ML_H__ +#define __SHF_RGFW_ML_H__ + +/* Copyright (C) 2004-2007 Henry Cejtin, Matthew Fluet, Suresh + * Jagannathan, and Stephen Weeks. + * + * MLton is released under a HPND-style license. + * See the file MLton-LICENSE for details. + */ + +#ifndef _MLTON_MLTYPES_H_ +#define _MLTON_MLTYPES_H_ + +/* We need these because in header files for exported SML functions, + * types.h is included without cenv.h. + */ +#if (defined (_AIX) || defined (__hpux__) || defined (__OpenBSD__)) +#include +#elif (defined (__sun__)) +#include +#else +#include +#endif + +/* ML types */ +typedef unsigned char PointerAux __attribute__ ((may_alias)); +typedef PointerAux* Pointer; +#define Array(t) Pointer +#define Ref(t) Pointer +#define Vector(t) Pointer + +typedef int8_t Int8_t; +typedef int8_t Int8; +typedef int16_t Int16_t; +typedef int16_t Int16; +typedef int32_t Int32_t; +typedef int32_t Int32; +typedef int64_t Int64_t; +typedef int64_t Int64; +typedef float Real32_t; +typedef float Real32; +typedef double Real64_t; +typedef double Real64; +typedef uint8_t Word8_t; +typedef uint8_t Word8; +typedef uint16_t Word16_t; +typedef uint16_t Word16; +typedef uint32_t Word32_t; +typedef uint32_t Word32; +typedef uint64_t Word64_t; +typedef uint64_t Word64; + +typedef Int8_t WordS8_t; +typedef Int8_t WordS8; +typedef Int16_t WordS16_t; +typedef Int16_t WordS16; +typedef Int32_t WordS32_t; +typedef Int32_t WordS32; +typedef Int64_t WordS64_t; +typedef Int64_t WordS64; + +typedef Word8_t WordU8_t; +typedef Word8_t WordU8; +typedef Word16_t WordU16_t; +typedef Word16_t WordU16; +typedef Word32_t WordU32_t; +typedef Word32_t WordU32; +typedef Word64_t WordU64_t; +typedef Word64_t WordU64; + +typedef WordU8_t Char8_t; +typedef WordU8_t Char8; +typedef WordU16_t Char16_t; +typedef WordU16_t Char16; +typedef WordU32_t Char32_t; +typedef WordU32_t Char32; + +typedef Vector(Char8_t) String8_t; +typedef Vector(Char8_t) String8; +typedef Vector(Char16_t) String16_t; +typedef Vector(Char16_t) String16; +typedef Vector(Char32_t) String32_t; +typedef Vector(Char32_t) String32; + +typedef Int32_t Bool_t; +typedef String8_t NullString8_t; +typedef String8_t NullString8; + +typedef void* CPointer; +typedef Pointer Objptr; + +#endif /* _MLTON_MLTYPES_H_ */ + +/* Copyright (C) 1999-2007 Henry Cejtin, Matthew Fluet, Suresh + * Jagannathan, and Stephen Weeks. + * Copyright (C) 1997-2000 NEC Research Institute. + * + * MLton is released under a HPND-style license. + * See the file MLton-LICENSE for details. + */ + +#ifndef _MLTON_EXPORT_H_ +#define _MLTON_EXPORT_H_ + +/* ------------------------------------------------- */ +/* Symbols */ +/* ------------------------------------------------- */ + +/* An external symbol is something not defined by the module + * (executable or library) being built. Rather, it is provided + * from a library dependency (dll, dylib, or shared object). + * + * A public symbol is defined in this module as being available + * to users outside of this module. If building a library, this + * means the symbol will be part of the public interface. + * + * A private symbol is defined within this module, but will not + * be made available outside of it. This is typically used for + * internal implementation details that should not be accessible. + */ + +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) +#define EXTERNAL __declspec(dllimport) +#define PUBLIC __declspec(dllexport) +#define PRIVATE +#else +#define EXTERNAL __attribute__((visibility("default"))) +#define PUBLIC __attribute__((visibility("default"))) +#define PRIVATE __attribute__((visibility("hidden"))) +#endif + +#endif /* _MLTON_EXPORT_H_ */ + +#if !defined(PART_OF_SHF_RGFW) && \ + !defined(STATIC_LINK_SHF_RGFW) && \ + !defined(DYNAMIC_LINK_SHF_RGFW) +#define PART_OF_SHF_RGFW +#endif + +#if defined(PART_OF_SHF_RGFW) +#define MLLIB_PRIVATE(x) PRIVATE x +#define MLLIB_PUBLIC(x) PUBLIC x +#elif defined(STATIC_LINK_SHF_RGFW) +#define MLLIB_PRIVATE(x) +#define MLLIB_PUBLIC(x) PUBLIC x +#elif defined(DYNAMIC_LINK_SHF_RGFW) +#define MLLIB_PRIVATE(x) +#define MLLIB_PUBLIC(x) EXTERNAL x +#else +#error Must specify linkage for shf_rgfw +#define MLLIB_PRIVATE(x) +#define MLLIB_PUBLIC(x) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +MLLIB_PUBLIC(void mltonEscape ();) +MLLIB_PUBLIC(void mltonBackspace ();) +MLLIB_PUBLIC(void mltonEnter ();) +MLLIB_PUBLIC(void mltonChar (Word8 x0);) +MLLIB_PUBLIC(void mltonResize (Int32 x0, Int32 x1);) + +#undef MLLIB_PRIVATE +#undef MLLIB_PUBLIC + +#ifdef __cplusplus +} +#endif + +#endif /* __SHF_RGFW_ML_H__ */ diff --git a/shf/ffi/rgfw-export.c b/shf/ffi/rgfw-export.c new file mode 100644 index 0000000..f708d33 --- /dev/null +++ b/shf/ffi/rgfw-export.c @@ -0,0 +1,380 @@ +#define RGFW_OPENGL +#define RGFW_ALLOC_DROPFILES +#define RGFW_IMPLEMENTATION +#define RGFW_PRINT_ERRORS +#define RGFW_DEBUG +#define GL_SILENCE_DEPRECATION +#include "RGFW.h" +#include +#include +#include +#include "mlton-rgfw-export.h" + +RGFW_window* createWindow(char* title, int x, int y, int width, int height) { + return RGFW_createWindow(title, x, y, width, height, RGFW_windowCenter | RGFW_windowOpenGL); +} + +void closeWindow(RGFW_window* window) { + RGFW_window_close(window); +} + +bool shouldCloseWindow(RGFW_window* window) { + if (RGFW_window_shouldClose(window)) { + return true; + } else { + return false; + } +} + +void swapBuffers(RGFW_window* window) { + RGFW_window_swapBuffers_OpenGL(window); +} + +void enableVsync(RGFW_window* window) { + // A swap interval of 1 will enable vsync + RGFW_window_swapInterval_OpenGL(window, 1); +} + +void writeClipboard(char* string, int stringSize) { + RGFW_writeClipboard(string, stringSize); +} + +void keyCallback(RGFW_window* window, unsigned char key, unsigned char symbol, unsigned char keymod, unsigned char repeated, unsigned char pressed) { + if (pressed || repeated) { + switch (key) { + case RGFW_escape: + mltonEscape(); + break; + case RGFW_backSpace: + mltonBackspace(); + break; + case RGFW_enter: + mltonEnter(); + break; + + case RGFW_a: + case RGFW_b: + case RGFW_c: + case RGFW_d: + case RGFW_e: + case RGFW_f: + case RGFW_g: + case RGFW_h: + case RGFW_i: + case RGFW_j: + case RGFW_k: + case RGFW_l: + case RGFW_m: + case RGFW_n: + case RGFW_o: + case RGFW_p: + case RGFW_q: + case RGFW_r: + case RGFW_s: + case RGFW_t: + case RGFW_u: + case RGFW_v: + case RGFW_w: + case RGFW_x: + case RGFW_y: + case RGFW_z: + if (keymod == RGFW_modShift) { + mltonChar(toupper((char)key)); + break; + } else { + mltonChar((char)key); + break; + } + + // These two don't have "shifted versions" + // so no need to turn to upper case + case RGFW_tab: + case RGFW_space: + mltonChar((char)key); + break; + + // Have to "shift" symbols manually + // because toupper(char) does not produce + // the desired character + case RGFW_backtick: + if (keymod == RGFW_modShift) { + mltonChar('~'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_0: + if (keymod == RGFW_modShift) { + mltonChar(')'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_1: + if (keymod == RGFW_modShift) { + mltonChar('!'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_2: + if (keymod == RGFW_modShift) { + mltonChar('@'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_3: + if (keymod == RGFW_modShift) { + mltonChar('#'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_4: + if (keymod == RGFW_modShift) { + mltonChar('$'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_5: + if (keymod == RGFW_modShift) { + mltonChar('%'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_6: + if (keymod == RGFW_modShift) { + mltonChar('^'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_7: + if (keymod == RGFW_modShift) { + mltonChar('&'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_8: + if (keymod == RGFW_modShift) { + mltonChar('*'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_9: + if (keymod == RGFW_modShift) { + mltonChar('('); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_minus: + if (keymod == RGFW_modShift) { + mltonChar('_'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_equal: + if (keymod == RGFW_modShift) { + mltonChar('+'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_period: + if (keymod == RGFW_modShift) { + mltonChar('>'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_comma: + if (keymod == RGFW_modShift) { + mltonChar('<'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_slash: + if (keymod == RGFW_modShift) { + mltonChar('?'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_bracket: + if (keymod == RGFW_modShift) { + mltonChar('{'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_closeBracket: + if (keymod == RGFW_modShift) { + mltonChar('}'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_semicolon: + if (keymod == RGFW_modShift) { + mltonChar(':'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_apostrophe: + if (keymod == RGFW_modShift) { + mltonChar('"'); + break; + } else { + mltonChar((char)key); + break; + } + case RGFW_backSlash: + if (keymod == RGFW_modShift) { + mltonChar('|'); + break; + } else { + mltonChar((char)key); + break; + } + } + } +} + +void setKeyCallback() { + RGFW_setKeyCallback(keyCallback); +} + +void resizeCallback(RGFW_window* window, int width, int height) { + glViewport(0, 0, width, height); + mltonResize(width, height); +} + +void setResizeCallback() { + RGFW_setWindowResizedCallback(resizeCallback); +} + +void pollEvents() { + RGFW_pollEvents(); +} + +// OpenGL constants used below +unsigned int VERTEX_SHADER = GL_VERTEX_SHADER; +unsigned int FRAGMENT_SHADER = GL_FRAGMENT_SHADER; +unsigned int TRIANGLES = GL_TRIANGLES; +unsigned int STATIC_DRAW = GL_STATIC_DRAW; +unsigned int DYNAMIC_DRAW = GL_DYNAMIC_DRAW; + +// OpenGL functions used below +void enableDepthTest() { + glEnable(GL_DEPTH_TEST); +} + +void viewport(int width, int height) { + glViewport(0, 0, width, height); +} + +void clearColor(float r, float g, float b, float a) { + glClearColor(r, g, b, a); +} + +void clear() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +unsigned int createBuffer() { + unsigned int buffer; + glGenBuffers(1, &buffer); + return buffer; +} + +void bindBuffer(unsigned int buffer) { + glBindBuffer(GL_ARRAY_BUFFER, buffer); +} + +void bufferData(float* vector, int vectorLength, unsigned int updateMode) { + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vectorLength, vector, updateMode); +} + +unsigned int createShader(unsigned int shaderType) { + return glCreateShader(shaderType); +} + +void shaderSource(unsigned int shader, const char *sourceString) { + glShaderSource(shader, 1, &sourceString, NULL); +} + +void compileShader(unsigned int shader) { + glCompileShader(shader); +} + +void deleteShader(unsigned int shader) { + glDeleteShader(shader); +} + +void vertexAttribPointer(int location, int numVecComponents, int stride, int offset) { + glVertexAttribPointer(location, numVecComponents, GL_FLOAT, GL_FALSE, stride * sizeof(float), (void*)offset); +} + +void enableVertexAttribArray(int location) { + glEnableVertexAttribArray(location); +} + +unsigned int createProgram() { + return glCreateProgram(); +} + +void attachShader(unsigned int program, unsigned int shader) { + glAttachShader(program, shader); +} + +void linkProgram(unsigned int program) { + glLinkProgram(program); +} + +void useProgram(unsigned int program) { + glUseProgram(program); +} + +void deleteProgram(unsigned int program) { + glDeleteProgram(program); +} + +void drawArrays(unsigned int drawMode, int startIndex, int numVertices) { + glDrawArrays(drawMode, startIndex, numVertices); +} + +int getUniformLocation(unsigned int program, const char *uniformName) { + glGetUniformLocation(program, uniformName); +} + +void uniform4f(int uniformLocation, float a, float b, float c, float d) { + glUniform4f(uniformLocation, a, b, c, d); +} diff --git a/shf/ffi/rgfw-import.sml b/shf/ffi/rgfw-import.sml new file mode 100644 index 0000000..6686a7e --- /dev/null +++ b/shf/ffi/rgfw-import.sml @@ -0,0 +1,37 @@ +structure Rgfw = +struct + type window = MLton.Pointer.t + + (* RGFW functions. *) + val createWindow = + _import "createWindow" public : string * int * int * int * int -> window; + val closeWindow = + _import "closeWindow" public : window -> unit; + val shouldCloseWindow = + _import "shouldCloseWindow" public : window -> bool; + val swapBuffers = + _import "swapBuffers" public : window -> unit; + val enableVsync = + _import "enableVsync" public : window -> unit; + val pollEvents = + _import "pollEvents" public reentrant : unit -> unit; + + val writeClipboard = + _import "writeClipboard" public : string * int -> unit; + + val exportEscapeCallback = + _export "mltonEscape" public : (unit -> unit) -> unit; + val exportBackspaceCallback = + _export "mltonBackspace" public : (unit -> unit) -> unit; + val exportEnterCallback = + _export "mltonEnter" public : (unit -> unit) -> unit; + val exportCharCallback = + _export "mltonChar" public : (char -> unit) -> unit; + val setKeyCallback = + _import "setKeyCallback" public : unit -> unit; + + val exportResizeCallback = + _export "mltonResize" public : (int * int -> unit) -> unit; + val setResizeCallback = + _import "setResizeCallback" public : unit -> unit; +end diff --git a/shf/message-types/draw-msg.sml b/shf/message-types/draw-msg.sml new file mode 100644 index 0000000..31bbb1e --- /dev/null +++ b/shf/message-types/draw-msg.sml @@ -0,0 +1,2 @@ +structure DrawMsg = +struct datatype t = DRAW_TEXT of Real32.real vector | YANK of string end diff --git a/shf/message-types/input-msg.sml b/shf/message-types/input-msg.sml new file mode 100644 index 0000000..6414c23 --- /dev/null +++ b/shf/message-types/input-msg.sml @@ -0,0 +1,13 @@ +structure InputMsg = +struct + datatype t = + CHAR_EVENT of char + | KEY_ESC + | KEY_ENTER + | KEY_BACKSPACE + | RESIZE_EVENT of int * int + | ARROW_LEFT + | ARROW_UP + | ARROW_RIGHT + | ARROW_DOWN +end diff --git a/shf/message-types/mailbox-type.sml b/shf/message-types/mailbox-type.sml new file mode 100644 index 0000000..c7f5749 --- /dev/null +++ b/shf/message-types/mailbox-type.sml @@ -0,0 +1 @@ +structure MailboxType = struct datatype t = DRAW of DrawMsg.t end diff --git a/shf/shell/draw-mailbox.sml b/shf/shell/draw-mailbox.sml new file mode 100644 index 0000000..92bca3e --- /dev/null +++ b/shf/shell/draw-mailbox.sml @@ -0,0 +1 @@ +structure DrawMailbox = MakeMailbox(DrawMsg) diff --git a/shf/shell/exception-logger.sml b/shf/shell/exception-logger.sml new file mode 100644 index 0000000..96ad17b --- /dev/null +++ b/shf/shell/exception-logger.sml @@ -0,0 +1,38 @@ +structure ExceptionLogger = +struct + open InputMsg + + val textCommands = ref "" + + fun addCommand inputMsg = + case inputMsg of + CHAR_EVENT chr => + let + val chr = CharVector.fromList [chr] + val newInput = !textCommands ^ chr + in + textCommands := newInput + end + | _ => () + + fun log e = + let + (* print stack trace for debugging purposes, + * and then raise another exception to exit the program *) + val errName = General.exnName e ^ "\n" + val stackTrace = MLton.Exn.history e + val stackTrace = (String.concatWith "\n" stackTrace) ^ "\n" + val history = !textCommands ^ "\n\n" + + val log = String.concat + ["ERROR: ", errName, stackTrace, "HISTORY: ", history] + + val () = print ("\n" ^ log) + + val io = TextIO.openAppend "exceptions.log" + val () = TextIO.output (io, log) + val () = TextIO.closeOut io + in + raise e + end +end diff --git a/shf/shell/gl-draw.sml b/shf/shell/gl-draw.sml new file mode 100644 index 0000000..fdcef71 --- /dev/null +++ b/shf/shell/gl-draw.sml @@ -0,0 +1,82 @@ +structure GlDraw = +struct + open DrawMsg + + type t = + { textVertexBuffer: Word32.word + , textProgram: Word32.word + , textDrawLength: int + } + + fun createShader (shaderType, shaderString) = + let + val shader = Gles3.createShader shaderType + val _ = Gles3.shaderSource (shader, shaderString) + val _ = Gles3.compileShader shader + in + shader + end + + fun createProgram (vertexShader, fragmentShader) = + let + val program = Gles3.createProgram () + val _ = Gles3.attachShader (program, vertexShader) + val _ = Gles3.attachShader (program, fragmentShader) + val _ = Gles3.linkProgram program + in + program + end + + fun create () = + let + (* create vertex buffer, program, etc. for text. *) + val textVertexBuffer = Gles3.createBuffer () + val xyzRgbVertexShader = createShader + (Gles3.VERTEX_SHADER, GlShaders.xyzRgbVertexShaderString) + + val rgbFragmentShader = createShader + (Gles3.FRAGMENT_SHADER, GlShaders.rgbFragmentShaderString) + + val textProgram = createProgram (xyzRgbVertexShader, rgbFragmentShader) + + (* clean up shaders which are no longer needed once progran is linked. *) + val _ = Gles3.deleteShader xyzRgbVertexShader + val _ = Gles3.deleteShader rgbFragmentShader + + (* because we only have a single vertex buffer, + * we only need to bind and set attributes once. *) + val _ = Gles3.bindBuffer textVertexBuffer + + (* enable xyz component from uploaded array *) + val _ = Gles3.vertexAttribPointer (0, 3, 6, 0) + val _ = Gles3.enableVertexAttribArray 0 + (* enable rgb component from uploaded array *) + val _ = Gles3.vertexAttribPointer (1, 3, 6, 12) + val _ = Gles3.enableVertexAttribArray 1 + + val _ = Gles3.useProgram textProgram + in + { textVertexBuffer = textVertexBuffer + , textProgram = textProgram + , textDrawLength = 0 + } + end + + fun uploadText (drawState: t, vec) = + let + val {textVertexBuffer, textProgram, textDrawLength = _} = drawState + + val _ = Gles3.bufferData (vec, Vector.length vec, Gles3.STATIC_DRAW) + val newTextDrawLength = Vector.length vec div 6 + in + { textVertexBuffer = textVertexBuffer + , textProgram = textProgram + , textDrawLength = newTextDrawLength + } + end + + fun draw (drawObject: t) = + let val {textVertexBuffer, textDrawLength, textProgram} = drawObject + in Gles3.drawArrays (Gles3.TRIANGLES, 0, textDrawLength) + end +end diff --git a/shf/shell/gl-shaders.sml b/shf/shell/gl-shaders.sml new file mode 100644 index 0000000..4c2e04b --- /dev/null +++ b/shf/shell/gl-shaders.sml @@ -0,0 +1,23 @@ +structure GlShaders = +struct + val xyzRgbVertexShaderString = + "#version 300 es\n\ + \layout (location = 0) in vec3 apos;\n\ + \layout (location = 1) in vec3 col;\n\ + \out vec3 frag_col;\n\ + \void main()\n\ + \{\n\ + \ frag_col = col;\n\ + \ gl_Position = vec4(apos.x, apos.y, apos.z, 1.0f);\n\ + \}" + + val rgbFragmentShaderString = + "#version 300 es\n\ + \precision mediump float;\n\ + \in vec3 frag_col;\n\ + \out vec4 FragColor;\n\ + \void main()\n\ + \{\n\ + \ FragColor = vec4(frag_col.x, frag_col.y, frag_col.z, 1.0f);\n\ + \}" +end diff --git a/shf/shell/glfw-gamepad.sml b/shf/shell/glfw-gamepad.sml new file mode 100644 index 0000000..70d938a --- /dev/null +++ b/shf/shell/glfw-gamepad.sml @@ -0,0 +1,264 @@ +structure GlfwGamepad = +struct + datatype mode = + PENDING + (* we need to wait for all keys to be released after pressing a button *) + | WAIT_FOR_KEY_RELEASE + | TRIANGLE + | TRIANGLE_CIRCLE + | CIRCLE + | CIRCLE_CROSS + | CROSS + | CROSS_SQUARE + | SQUARE + (* maybe SQUARE_TRIANGLE for numbers and symbols? *) + | SQUARE_TRIANGLE + + structure IM = InputMsg + + type gamepad_state = + { mode: mode + , shiftChr: bool + , trianglePressed: bool + , circlePressed: bool + , crossPressed: bool + , squarePressed: bool + } + + fun releaseKeysAndUnshift (gamepadState: gamepad_state) = + let + val + { mode = _ + , shiftChr = _ + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + } = gamepadState + in + { mode = WAIT_FOR_KEY_RELEASE + , shiftChr = false + , trianglePressed = false + , circlePressed = false + , crossPressed = false + , squarePressed = false + } + end + + fun onWaitForKeyRelease + ( gamepadState: gamepad_state + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , actions + ) = + if + trianglePressed orelse circlePressed orelse crossPressed + orelse squarePressed + then + (gamepadState, actions) + else + let + val newState = + { mode = PENDING + , shiftChr = #shiftChr gamepadState + , trianglePressed = false + , circlePressed = false + , crossPressed = false + , squarePressed = false + } + in + (newState, actions) + end + + fun onPendingMode + ( state: gamepad_state + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , actions: IM.t list + ) = + if + trianglePressed orelse circlePressed orelse crossPressed + orelse squarePressed + then + (* some button is being pressed, + * so we record that in the returned state, + * in addition to whatever buttons were previously pressed *) + let + val trianglePressed = #trianglePressed state orelse trianglePressed + val circlePressed = #circlePressed state orelse circlePressed + val crossPressed = #crossPressed state orelse crossPressed + val squarePressed = #squarePressed state orelse squarePressed + + val newState = + { mode = #mode state + , shiftChr = #shiftChr state + , trianglePressed = trianglePressed + , circlePressed = circlePressed + , crossPressed = crossPressed + , squarePressed = squarePressed + } + in + (newState, actions) + end + else + (* nothing is currently pressed, + * so we check if there is a valid mode indicated by the state + * and change the mode if so *) + let + val + { trianglePressed + , circlePressed + , crossPressed + , squarePressed + , shiftChr + , mode = _ + } = state + val newMode = + if trianglePressed andalso not (crossPressed orelse squarePressed) then + if not circlePressed then TRIANGLE else TRIANGLE_CIRCLE + else if circlePressed andalso not squarePressed then + if not crossPressed then CIRCLE else CIRCLE_CROSS + else if crossPressed then + if not squarePressed then CROSS else CROSS_SQUARE + else if squarePressed then + if not trianglePressed then SQUARE else SQUARE_TRIANGLE + else + (* some buttons are being pressed, + * but not a valid combination to switch + * to another mode, so we are still on PENDING *) + PENDING + + val newState = + { mode = newMode + , shiftChr = shiftChr + , trianglePressed = false + , circlePressed = false + , crossPressed = false + , squarePressed = false + } + in + (newState, actions) + end + + fun onTriangleMode + ( gamepadState + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , actions + ) = + if trianglePressed then + let + val newState = releaseKeysAndUnshift gamepadState + val actions = IM.CHAR_EVENT #"a" :: actions + in + (newState, actions) + end + else if circlePressed then + let + val newState = releaseKeysAndUnshift gamepadState + val actions = IM.CHAR_EVENT #"b" :: actions + in + (newState, actions) + end + else if crossPressed then + let + val newState = releaseKeysAndUnshift gamepadState + val actions = IM.CHAR_EVENT #"c" :: actions + in + (newState, actions) + end + else if squarePressed then + let + val newState = releaseKeysAndUnshift gamepadState + val actions = IM.CHAR_EVENT #"d" :: actions + in + (newState, actions) + end + else + (gamepadState, actions) + + fun handleButtons + ( gamepadState + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , l1Pressed + , r1Pressed + ) = + let + val actions = if l1Pressed then [IM.KEY_BACKSPACE] else [] + val actions = + if r1Pressed then (IM.CHAR_EVENT #" ") :: actions else actions + in + case #mode gamepadState of + PENDING => + onPendingMode + ( gamepadState + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , actions + ) + | WAIT_FOR_KEY_RELEASE => + onWaitForKeyRelease + ( gamepadState + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , actions + ) + | TRIANGLE => + onTriangleMode + ( gamepadState + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , actions + ) + end + + (* impure functions below *) + fun getGamepadState () = + Input.getGamepadState 0 <> 0 orelse Input.getGamepadState 1 <> 0 + orelse Input.getGamepadState 2 <> 0 orelse Input.getGamepadState 3 <> 0 + orelse Input.getGamepadState 4 <> 0 orelse Input.getGamepadState 5 <> 0 + orelse Input.getGamepadState 6 <> 0 orelse Input.getGamepadState 7 <> 0 + orelse Input.getGamepadState 8 <> 0 orelse Input.getGamepadState 9 <> 0 + orelse Input.getGamepadState 10 <> 0 orelse Input.getGamepadState 11 <> 0 + orelse Input.getGamepadState 12 <> 0 orelse Input.getGamepadState 13 <> 0 + orelse Input.getGamepadState 14 <> 0 orelse Input.getGamepadState 15 <> 0 + + fun query gamepadState = + if getGamepadState () then + let + val trianglePressed = Input.isTriangleButtonPressed () <> 0 + val circlePressed = Input.isCircleButtonPressed () <> 0 + val crossPressed = Input.isCrossButtonPressed () <> 0 + val squarePressed = Input.isSquareButtonPressed () <> 0 + val l1Pressed = Input.isL1ButtonPressed () <> 0 + val r1Pressed = Input.isR1ButtonPressed () <> 0 + in + handleButtons + ( gamepadState + , trianglePressed + , circlePressed + , crossPressed + , squarePressed + , l1Pressed + , r1Pressed + ) + end + else + (* nothing to do, so return same state *) + (gamepadState, []) +end diff --git a/shf/shell/glfw-loop.sml b/shf/shell/glfw-loop.sml new file mode 100644 index 0000000..d6ffa13 --- /dev/null +++ b/shf/shell/glfw-loop.sml @@ -0,0 +1,174 @@ +structure GlfwLoop = +struct + fun yank (window, str) = + let + (* print when text is yanked + * because GLFW currently has a bug on Wayland + * when setting the clipboard string *) + val msg = "|" ^ String.toCString str ^ "|\n" + val () = print msg + val () = Glfw.setClipboardString (window, str) + in + () + end + + fun consumeEvent (drawState, window, msg) = + let + open DrawMsg + + val {textVertexBuffer, textProgram, textDrawLength = _, ...} = drawState + in + case msg of + DRAW_TEXT textVec => GlDraw.uploadText (drawState, textVec) + | YANK str => (yank (window, str); drawState) + end + + fun consumeEventsLoop (pos, msgVec, drawState, window) = + if pos = Vector.length msgVec then + drawState + else + let + val msg = Vector.sub (msgVec, pos) + val drawState = consumeEvent (drawState, window, msg) + in + consumeEventsLoop (pos + 1, msgVec, drawState, window) + end + + fun consumeEvents (drawState, window) = + consumeEventsLoop (0, DrawMailbox.getMessagesAndClear (), drawState, window) + + fun helpLoop (app, drawState, window, gamepad) = + case Glfw.windowShouldClose window of + false => + let + val drawState = consumeEvents (drawState, window) + + val _ = Gles3.clearColor (0.89, 0.89, 0.89, 1.0) + val _ = Gles3.clear () + + (* one update reacting to gamepad events *) + val (gamepad, actions) = GlfwGamepad.query gamepad + val app = Updater.updateLoop (0, Vector.fromList actions, app) + + (* one update reacting to keyboard events *) + val app = Updater.update app + val _ = GlDraw.draw drawState + + val _ = Glfw.swapBuffers window + val _ = Glfw.waitEvents () + in + helpLoop (app, drawState, window, gamepad) + end + | true => Glfw.terminate () + + fun loop (app, window) = + let + val drawState = GlDraw.create () + + val gamepad: GlfwGamepad.gamepad_state = + { mode = GlfwGamepad.PENDING + , shiftChr = false + , trianglePressed = false + , circlePressed = false + , crossPressed = false + , squarePressed = false + } + in + helpLoop (app, drawState, window, gamepad) + end + + open InputMsg + + fun frameBufferSizeCallback (width, height) = + InputMailbox.append (RESIZE_EVENT (width, height)) + + fun charCallback word = + let + val word = Word32.toInt word + val chr = Char.chr word + in + InputMailbox.append (CHAR_EVENT chr) + end + + fun keyCallback (key, scancode, action, mods) = + let + open Input + in + if key = KEY_ESC andalso action = PRESS andalso mods = 0 then + InputMailbox.append (InputMsg.KEY_ESC) + else if key = KEY_ENTER andalso action = PRESS andalso mods = 0 then + InputMailbox.append (InputMsg.KEY_ENTER) + else if key = KEY_BACKSPACE andalso action <> RELEASE andalso mods = 0 then + InputMailbox.append (InputMsg.KEY_BACKSPACE) + else if key = KEY_ARROW_LEFT andalso action <> RELEASE andalso mods = 0 then + InputMailbox.append (InputMsg.ARROW_LEFT) + else if key = KEY_ARROW_RIGHT andalso action <> RELEASE andalso mods = 0 then + InputMailbox.append (InputMsg.ARROW_RIGHT) + else if key = KEY_ARROW_UP andalso action <> RELEASE andalso mods = 0 then + InputMailbox.append (InputMsg.ARROW_UP) + else if key = KEY_ARROW_DOWN andalso action <> RELEASE andalso mods = 0 then + InputMailbox.append (InputMsg.ARROW_DOWN) + else + () + end + + fun registerCallbacks window = + let + val () = Input.exportFramebufferSizeCallback frameBufferSizeCallback + val () = Input.setFramebufferSizeCallback window + + val () = Input.exportCharCallback charCallback + val () = Input.setCharCallback window + + val () = Input.exportKeyCallback keyCallback + val () = Input.setKeyCallback window + in + () + end + + local + fun loop (io, acc, lastCharWasNewline) = + case TextIO.inputLine io of + SOME str => + let + val endsWithNewline = + String.size str > 0 + andalso String.sub (str, String.size str - 1) = #"\n" + in + loop (io, LineGap.append (str, acc), endsWithNewline) + end + | NONE => + if lastCharWasNewline then + LineGap.goToStart acc + else + let val acc = LineGap.append ("\n", acc) + in LineGap.goToStart acc + end + in + fun ioToLineGap (io, acc) = loop (io, acc, false) + end + + fun main () = + let + (* Set up GLFW. *) + val _ = Glfw.init () + val _ = Glfw.windowHint (Glfw.CONTEXT_VERSION_MAJOR (), 3) + val _ = Glfw.windowHint (Glfw.DEPRECATED (), Glfw.FALSE ()) + val window = Glfw.createWindow (1920, 1080, "shf") + val _ = Glfw.makeContextCurrent window + val _ = Glfw.loadGlad () + val _ = Gles3.enableDepthTest () + + (* load file intol gap buffer and create initial app *) + val io = TextIO.openIn "temp.txt" + val lineGap = ioToLineGap (io, LineGap.empty) + val _ = TextIO.closeIn io + val app = AppType.init (lineGap, 1920, 1080, Time.now ()) + + val () = registerCallbacks window + in + loop (app, window) + end +end + +val _ = GlfwLoop.main () diff --git a/shf/shell/input-mailbox.sml b/shf/shell/input-mailbox.sml new file mode 100644 index 0000000..8907262 --- /dev/null +++ b/shf/shell/input-mailbox.sml @@ -0,0 +1 @@ +structure InputMailbox = MakeMailbox(InputMsg) diff --git a/shf/shell/make-mailbox.sml b/shf/shell/make-mailbox.sml new file mode 100644 index 0000000..ba37eca --- /dev/null +++ b/shf/shell/make-mailbox.sml @@ -0,0 +1,29 @@ +signature MAKE_MAILBOX = +sig + type t +end + +functor MakeMailbox(Fn: MAKE_MAILBOX) = +struct + val messages: Fn.t vector ref = ref #[] + + fun getMessagesAndClear () = + let + val () = MLton.Thread.atomicBegin () + val msgs = !messages + val () = messages := #[] + val () = MLton.Thread.atomicEnd () + in + msgs + end + + fun append newMsg = + let + val () = MLton.Thread.atomicBegin () + val msgs = !messages + val msgs = Vector.concat [msgs, #[newMsg]] + val () = messages := msgs + in + MLton.Thread.atomicEnd () + end +end diff --git a/shf/shell/rgfw-loop.sml b/shf/shell/rgfw-loop.sml new file mode 100644 index 0000000..a9199dc --- /dev/null +++ b/shf/shell/rgfw-loop.sml @@ -0,0 +1,118 @@ +structure RgfwLoop = +struct + fun yank string = + Rgfw.writeClipboard (string, String.size string) + + fun consumeEvent (drawState, msg) = + let + open DrawMsg + + val {textVertexBuffer, textProgram, textDrawLength = _, ...} = drawState + in + case msg of + DRAW_TEXT textVec => GlDraw.uploadText (drawState, textVec) + | YANK str => (yank str; drawState) + end + + fun consumeEventsLoop (pos, msgVec, drawState) = + if pos = Vector.length msgVec then + drawState + else + let + val msg = Vector.sub (msgVec, pos) + val drawState = consumeEvent (drawState, msg) + in + consumeEventsLoop (pos + 1, msgVec, drawState) + end + + fun consumeEvents drawState = + consumeEventsLoop (0, DrawMailbox.getMessagesAndClear (), drawState) + + fun loop (window, app, drawState) = + if Rgfw.shouldCloseWindow window then + Rgfw.closeWindow window + else + let + val () = Gles3.clearColor (0.89, 0.89, 0.89, 1.0) + val () = Gles3.clear () + + val () = Rgfw.pollEvents () + + val app = Updater.update app + + val drawState = consumeEvents drawState + val () = GlDraw.draw drawState + val () = Rgfw.swapBuffers window + in + loop (window, app, drawState) + end + + local + fun loop (io, acc, lastCharWasNewline) = + case TextIO.inputLine io of + SOME str => + let + val endsWithNewline = + String.size str > 0 + andalso String.sub (str, String.size str - 1) = #"\n" + in + loop (io, LineGap.append (str, acc), endsWithNewline) + end + | NONE => + if lastCharWasNewline then + LineGap.goToStart acc + else + let val acc = LineGap.append ("\n", acc) + in LineGap.goToStart acc + end + in + fun ioToLineGap (io, acc) = loop (io, acc, false) + end + + fun escapeCallback () = InputMailbox.append InputMsg.KEY_ESC + + fun backspaceCallback () = InputMailbox.append InputMsg.KEY_BACKSPACE + + fun enterCallback () = InputMailbox.append InputMsg.KEY_ENTER + + fun charCallback chr = + InputMailbox.append (InputMsg.CHAR_EVENT chr) + + fun resizeCallback (width, height) = + InputMailbox.append (InputMsg.RESIZE_EVENT (width, height)) + + fun registerCallbacks () = + let + val () = Rgfw.exportEscapeCallback escapeCallback + val () = Rgfw.exportBackspaceCallback backspaceCallback + val () = Rgfw.exportEnterCallback enterCallback + val () = Rgfw.exportCharCallback charCallback + val () = Rgfw.setKeyCallback () + + val () = Rgfw.exportResizeCallback resizeCallback + val () = Rgfw.setResizeCallback () + in + () + end + + fun main () = + let + val window = Rgfw.createWindow ("shf", 0, 0, 1920, 1080) + val () = Rgfw.enableVsync window + val () = Gles3.enableDepthTest () + + (* load file intol gap buffer and create initial app *) + val io = TextIO.openIn "temp.txt" + val lineGap = ioToLineGap (io, LineGap.empty) + val () = TextIO.closeIn io + + val () = registerCallbacks () + + val app = AppType.init (lineGap, 1920, 1080, Time.now ()) + val drawState = GlDraw.create () + in + loop (window, app, drawState) + end +end + +val _ = RgfwLoop.main () diff --git a/shf/shell/updater.sml b/shf/shell/updater.sml new file mode 100644 index 0000000..05ecc32 --- /dev/null +++ b/shf/shell/updater.sml @@ -0,0 +1,47 @@ +structure Updater = +struct + open MailboxType + open InputMsg + + fun sendMsg msg = + case msg of DRAW msg => DrawMailbox.append msg + + fun sendMsgs msgList = + case msgList of + hd :: tl => let val () = sendMsg hd in sendMsgs tl end + | [] => () + + fun updateOne (app: AppType.app_type, inputMsg) = + let + val time = Time.now () + + val () = + case inputMsg of + CHAR_EVENT #"~" => + ExceptionLogger.log (Fail "intentionally caused exception") + | _ => () + + val () = ExceptionLogger.addCommand inputMsg + + val app = AppUpdate.update (app, inputMsg, time) + handle e => ExceptionLogger.log e + + val () = sendMsgs (#msgs app) + in + app + end + + fun updateLoop (pos, msgVec, app) = + if pos = Vector.length msgVec then + app + else + let + val msg = Vector.sub (msgVec, pos) + val app = updateOne (app, msg) + in + updateLoop (pos + 1, msgVec, app) + end + + fun update app = + updateLoop (0, InputMailbox.getMessagesAndClear (), app) +end diff --git a/shf/shf-glfw.mlb b/shf/shf-glfw.mlb new file mode 100644 index 0000000..52b500a --- /dev/null +++ b/shf/shf-glfw.mlb @@ -0,0 +1,29 @@ +$(SML_LIB)/basis/basis.mlb + +fcore.mlb + +(* IMPERATIVE SHELL *) +$(SML_LIB)/basis/mlton.mlb + +ann + "allowFFI true" +in + ffi/gles3-import.sml + ffi/glfw-import.sml + ffi/glfw-input.sml +end + +ann + "allowVectorExps true" +in + shell/make-mailbox.sml +end +shell/input-mailbox.sml +shell/draw-mailbox.sml + +shell/exception-logger.sml +shell/updater.sml +shell/glfw-gamepad.sml +shell/gl-shaders.sml +shell/gl-draw.sml +shell/glfw-loop.sml diff --git a/shf/shf-rgfw.mlb b/shf/shf-rgfw.mlb new file mode 100644 index 0000000..5412487 --- /dev/null +++ b/shf/shf-rgfw.mlb @@ -0,0 +1,27 @@ +$(SML_LIB)/basis/basis.mlb + +fcore.mlb + +(* IMPERATIVE SHELL *) +$(SML_LIB)/basis/mlton.mlb +ann + "allowFFI true" +in + ffi/gles3-import.sml + ffi/rgfw-import.sml +end + +ann + "allowVectorExps true" +in + shell/make-mailbox.sml +end + +shell/input-mailbox.sml +shell/draw-mailbox.sml + +shell/exception-logger.sml +shell/updater.sml +shell/gl-shaders.sml +shell/gl-draw.sml +shell/rgfw-loop.sml diff --git a/shf/shf-tests.mlb b/shf/shf-tests.mlb new file mode 100644 index 0000000..29a6c6e --- /dev/null +++ b/shf/shf-tests.mlb @@ -0,0 +1,17 @@ +$(SML_LIB)/basis/basis.mlb + +fcore.mlb + +(* TEST FILES *) +$(SML_LIB)/basis/mlton.mlb +shell/exception-logger.sml +test/Railroad/src/railroad.mlb + +test/persistent-vector-tests.sml +test/regex-tests.sml +test/test-utils.sml +test/normal-move-tests.sml +test/normal-delete-tests.sml +test/normal-yank-tests.sml +test/regression-tests.sml +test/test.sml diff --git a/shf/temp.txt b/shf/temp.txt new file mode 100644 index 0000000..a2c1105 --- /dev/null +++ b/shf/temp.txt @@ -0,0 +1,2 @@ +hello hello hello +world world world diff --git a/shf/test/README.md b/shf/test/README.md new file mode 100644 index 0000000..ed8385f --- /dev/null +++ b/shf/test/README.md @@ -0,0 +1,7 @@ +# shf-tests + +Unit tests for shf. + +The tests require (Railroad)[https://github.com/PerplexSystems/Railroad] testing framework. + + diff --git a/shf/test/Railroad b/shf/test/Railroad new file mode 160000 index 0000000..b5aa94a --- /dev/null +++ b/shf/test/Railroad @@ -0,0 +1 @@ +Subproject commit b5aa94a8801f4f64afc3eb63f6a4a8ce57bef609 diff --git a/shf/test/normal-delete-tests.sml b/shf/test/normal-delete-tests.sml new file mode 100644 index 0000000..a88537f --- /dev/null +++ b/shf/test/normal-delete-tests.sml @@ -0,0 +1,7925 @@ +structure NormalDeleteTests = +struct + open Railroad + open Railroad.Test + open InputMsg + + + fun printVec pv = + let + val outputList = PersistentVector.toList pv + val str = + List.map + (fn {start, finish} => + "{start = " ^ Int.toString start ^ ", finish = " + ^ Int.toString finish ^ "}") outputList + val str = String.concatWith "\n " str ^ "\n" + in + print str + end + + + val dhDelete = describe "delete motion 'dh'" + [ test "does not delete when cursor is at index 0" (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.updateMany (app, "dh") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString (#buffer app) + in + Expect.isTrue (expectedString = actualString) + end) + , test "does not delete when character before cursor is a newline" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.updateMany (app, "dh") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString (#buffer app) + in + Expect.isTrue (expectedString = actualString) + end) + , test "deletes one char to the left when on a non-newline" (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val app = TestUtils.updateMany (app, "dh") + + (* assert *) + val expectedString = "hell world\n" + val actualString = LineGap.toString (#buffer app) + in + Expect.isTrue (expectedString = actualString) + end) + , test "moves cursor left by one after deleting left char" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.updateMany (app, "dh") + in + (* assert *) + Expect.isTrue (cursorIdx = originalIdx - 1) + end) + , test "deletes 3 chars and moves cursor left by 3 when count is 3" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "3dh") + + (* assert *) + val expectedString = "he world\n" + val deleted3CharsInString = expectedString = LineGap.toString buffer + val cursorIdxIsDecrementedBy3 = cursorIdx = originalIdx - 3 + in + Expect.isTrue + (cursorIdxIsDecrementedBy3 andalso deleted3CharsInString) + end) + , test + "deletes until start column when \ + \count is greater than current column" + (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "9dh") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = " world\n" + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/he") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dh") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list by 1 when we delete a single char" (fn _ => + let + (* arrange *) + val originalIdx = 1 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/wo") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dh") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val expectedSearchList = + List.map + (fn {start, finish} => {start = start - 1, finish = finish - 1}) + oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + in + Expect.isTrue (newSearchList = expectedSearchList) + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/helloworld") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dh") + + (* assert *) + val expectedSearchList = [{start = 0, finish = 9}] + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val assertion = + PersistentVector.isEmpty (#searchList app) + andalso newSearchList = expectedSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend \ + \after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello one\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dh") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val expectedOldSearchList = + [{start = 4, finish = 4}, {start = 6, finish = 6}] + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedNewSearchList = [{start = 4, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 3 + val originalString = "hello one\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dh") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val assertion = + oldSearchList = [{start = 0, finish = 4}] + andalso PersistentVector.isEmpty (#searchList newApp) + in + Expect.isTrue assertion + end) + ] + + (* 'dl' motion and 'x' motion have identical behaviour *) + val dlDelete = describe "delete motion 'dl'" + [ test + "deletes last char and moves cursor back by 1 \ + \when next char is a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 4 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dl") + + (* assert *) + val expectedString = "hell\nworld\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = originalIdx - 1 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "deletes char that cursor is currently on when not on newline" + (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "ello world\n" + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "does not delete any characters or move the cursor when on a newline" + (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello\n\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = originalIdx + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "does not delete past newline when specifying a range \ + \greater than number of columns" + (fn _ => + let + (* arrange *) + val originalIdx = 2 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "33dl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "he\nworld\n" + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = 1 + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "moves cursor to last newline after deleting \ + \all non-newline chars on last line \ + \and the original string ends with a newline" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "33dl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\n\n" + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = String.size expectedString - 1 + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "moves cursor to last newline after deleting \ + \all non-newline chars on last line \ + \and the original string does not end with a newline" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "33dl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\n" + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = 4 + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/he") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dl") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list by 1 when we delete a single char" (fn _ => + let + (* arrange *) + val originalIdx = 1 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/wo") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dl") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val expectedSearchList = + List.map + (fn {start, finish} => {start = start - 1, finish = finish - 1}) + oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + in + Expect.isTrue (newSearchList = expectedSearchList) + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/helloworld") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dl") + + (* assert *) + val expectedSearchList = [{start = 0, finish = 9}] + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val assertion = + PersistentVector.isEmpty (#searchList app) + andalso newSearchList = expectedSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend \ + \after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello one\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dl") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val expectedOldSearchList = + [{start = 4, finish = 4}, {start = 6, finish = 6}] + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedNewSearchList = [{start = 4, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer \after buffer is deleted" + (fn _ => + let + (* arrange *) + val originalIdx = 3 + val originalString = "hello one\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dl") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val assertion = + oldSearchList = [{start = 0, finish = 4}] + andalso PersistentVector.isEmpty (#searchList newApp) + in + Expect.isTrue assertion + end) + ] + + val djDelete = describe "delete motion 'dj'" + [ test "does not delete when cursor is on last line" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = String.size originalString - 3 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dj") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = originalIdx + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "does not delete when there is only one line" (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dj") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = originalIdx + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "deletes when cursor is on first line and there are at least two lines" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dj") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "deletes first two lines when there are three lines \ + \and cursor is on first line" + (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello\nworld\nbye world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dj") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "bye world\n" + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "deletes last two lines when there are three lines \ + \and cursor is on second line" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\nbye world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dj") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\n" + val stringIsExpected = actualString = expectedString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = cursorIdx = expectedCursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "leaves a newline at the end when deleting the whole file \ + \and file ends with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "33dj") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "leaves a newline at the end when deleting the whole file \ + \and file does not end with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "33dj") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "deletes two lines when cursor is on a newline" (fn _ => + let + (* arrange *) + val originalString = "\nhello\nworld\ntrello\nbrillo\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dj") + + (* assert *) + val expectedString = "world\ntrello\nbrillo\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello\nworld\ntest\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/he") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dj") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete lines preceding match" + (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello\nworld\ntest\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/test") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dj") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 12, finish = 15}] + val expectedNewSearchList = [{start = 0, finish = 3}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\ntest\nagain\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\nag") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dj") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\noorld\ntest\nooooo\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o\no+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dj") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 4, finish = 7}] + val expectedNewSearchList = [{start = 4, finish = 10}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\ntest\nagain\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\nworld") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dj") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val ddDelete = describe "delete motion 'dd'" + [ test + "deletes last line when there is more than one line \ + \and cursor is on last line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = String.size originalString - 3 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dd") + + (* assert *) + val expectedString = "hello\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "leaves a newline in the buffer \ + \when deleting and there is only one line" + (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dd") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "deletes when cursor is on first line and there are at least two lines" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dd") + + (* assert *) + val expectedString = "world\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "leaves a newline at the end when deleting the whole file using a count \ + \and file ends with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "33dd") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "leaves a newline at the end when deleting the whole file \ + \and file does not end with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "33dd") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test + "deletes just newline when cursor is on a line \ + \that contains only a single newline" + (fn _ => + let + (* arrange *) + val originalString = "\nhello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {cursorIdx, buffer, ...} = TestUtils.updateMany (app, "dd") + + (* assert *) + val expectedString = "hello\nworld\n" + val actualString = LineGap.toString buffer + val stringIsExpected = expectedString = actualString + + val expectedCursorIdx = 0 + val cursorIdxIsExpected = expectedCursorIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIdxIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 13 + val originalString = "hello\nworld\ntest\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dd") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete line preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello\nworld\ntest\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dd") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 0, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\ntest\nagain\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\nte") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dd") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\norange\noops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o\no+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dd") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 4, finish = 6}] + val expectedNewSearchList = [{start = 4, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\ntest\nagain\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\nworld") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dd") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dkDelete = describe "delete motion 'dk'" + [ test "does not delete when cursor is on first line" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString buffer + + val expectedIdx = 0 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes first two lines, leaving a newline in the buffer \ + \ when there are two lines \and cursor is on second line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 6 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + + val expectedIdx = 0 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes last two lines when there are three lines in the buffer \ + \ and cursor is on third line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val originalIdx = 15 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = "hello\n" + val actualString = LineGap.toString buffer + + val expectedIdx = 0 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "leaves a buffer containing just a newline when\ + \deleting from last line with a counter greater than total lines" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val originalIdx = 15 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "33dk") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + + val expectedIdx = 0 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes newline and preceding line when cursor is second line \ + \and second line contains only a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\n\nagain\n" + val originalIdx = 6 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = "again\n" + val actualString = LineGap.toString buffer + + val expectedIdx = 0 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes just newline and line above when cursor is on third line \ + \and third line contains only a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\n\nagain\n" + val originalString = + "hello\n\ + \world\n\ + \\n\ + \trello\n\ + \brillo\n" + val originalIdx = 12 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = + "hello\n\ + \trello\n\ + \brillo\n" + val actualString = LineGap.toString buffer + + val expectedIdx = 6 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes second and third lines when cursor is on \ + \last non-newline character of third line" + (fn _ => + let + (* arrange *) + val originalString = "hello\n\nagain\n" + val originalString = + "hello\n\ + \world\n\ + \trello\n\ + \brillo\n" + val originalIdx = 17 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = + "hello\n\ + \brillo\n" + val actualString = LineGap.toString buffer + + val expectedIdx = 6 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes last two lines when cursor is on last line \ + \and last line only has a newline" + (fn _ => + let + (* arrange *) + val originalString = + "hello\n\ + \world\n\ + \\n" + val originalIdx = String.size originalString - 1 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = "hello\n" + val actualString = LineGap.toString buffer + + val expectedIdx = 0 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "moves cursor to newline at end of file \ + \when cursor is two lines below" + (fn _ => + let + (* arrange *) + val originalString = + "hello\n\ + \world\n\ + \\n\ + \world\n\ + \hello\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dk") + + (* assert *) + val expectedString = + "hello\n\ + \world\n\ + \\n" + val actualString = LineGap.toString buffer + + val expectedIdx = String.size expectedString - 1 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 13 + val originalString = "hello\nworld\ntest\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dk") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete line preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\ntest\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/test") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dk") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 12, finish = 15}] + val expectedNewSearchList = [{start = 0, finish = 3}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 13 + val originalString = "hello\nworld\ntest\nagain\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\nag") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dk") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 13 + val originalString = "hello\nopple\ncarrot\nooorange\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o\no+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dk") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 4, finish = 6}] + val expectedNewSearchList = [{start = 4, finish = 8}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 13 + val originalString = "hello\nworld\ntest\nagain\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world\ntest") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dk") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 15}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dwDelete = describe "delete motion 'dw'" + [ test + "deletes last char and moves cursor back by one \ + \when used on last char of last word in buffer \ + \and buffer ends with a newline preceded by a non-newline" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "hello worl\n" + val actualString = LineGap.toString buffer + + val expectedIdx = String.size expectedString - 2 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test "deletes second word as expected when there are three words" (fn _ => + let + (* arrange *) + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "dw") + val newApp2 = TestUtils.updateMany (app2, "dw") + val newApp3 = TestUtils.updateMany (app3, "dw") + val newApp4 = TestUtils.updateMany (app4, "dw") + val newApp5 = TestUtils.updateMany (app5, "dw") + + (* assert *) + val expectedString1 = "hello again\n" + val expectedString2 = "hello wagain\n" + val expectedString3 = "hello woagain\n" + val expectedString4 = "hello woragain\n" + val expectedString5 = "hello worlagain\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + + val expectedCursor1 = 6 + val expectedCursor2 = 7 + val expectedCursor3 = 8 + val expectedCursor4 = 9 + val expectedCursor5 = 10 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test "does not delete newline following word" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "\nworld\nagain\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first punctuation char when on an alpha char \ + \and there is no space between alpha and punctuation" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "!world!again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first alpha char when on punctuation \ + \and there is no space between punctuation and alpha" + (fn _ => + let + (* arrange *) + val originalString = "!#%&QWERTY#!\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "QWERTY#!\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first alpha char \ + \when cursor is on space and next char is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "hello\n" + val actualString = LineGap.toString buffer + val expectedCursor = 1 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first alpha char \ + \when cursor is on space, many spaces are ahead, \ + \and first char after spaces is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "h ello\n" + val actualString = LineGap.toString buffer + val expectedCursor = 3 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first punctuation char \ + \when cursor is on space and next non-space char is punctuation" + (fn _ => + let + (* arrange *) + val originalString = "! @#$%\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "! @#$%\n" + val actualString = LineGap.toString buffer + val expectedCursor = 2 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes last char when on last word \ + \and there is no newline after current word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world" + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") + + (* assert *) + val expectedString = "hello " + val actualString = LineGap.toString buffer + + val expectedIdx = 5 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = cursorIdx = expectedIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dw") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dw") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 0, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello ag") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dw") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dw") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 4, finish = 6}] + val expectedNewSearchList = [{start = 4, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world test") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dw") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 15}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dWDelete = describe "delete motion 'dW'" + [ test + "deletes last char and moves cursor back by one \ + \when used on last char of last word in buffer \ + \and buffer ends with a newline preceded by a non-newline" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "hello worl\n" + val actualString = LineGap.toString buffer + + val expectedIdx = String.size expectedString - 2 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test "deletes second WORD as expected when there are three words" (fn _ => + let + (* arrange *) + (* 'l' in world is replaced with '!' + * so it fits with the definition of a WORD *) + val originalString = "hello wor!d again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "dW") + val newApp2 = TestUtils.updateMany (app2, "dW") + val newApp3 = TestUtils.updateMany (app3, "dW") + val newApp4 = TestUtils.updateMany (app4, "dW") + val newApp5 = TestUtils.updateMany (app5, "dW") + + (* assert *) + val expectedString1 = "hello again\n" + val expectedString2 = "hello wagain\n" + val expectedString3 = "hello woagain\n" + val expectedString4 = "hello woragain\n" + val expectedString5 = "hello wor!again\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + + val expectedCursor1 = 6 + val expectedCursor2 = 7 + val expectedCursor3 = 8 + val expectedCursor4 = 9 + val expectedCursor5 = 10 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test "does not delete newline following WORD" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "\nworld\nagain\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "does not delete newline when there is a newline after current word \ + \and there is another word following that newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "h\nworld\nagain\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes past first punctuation char \ + \when on an alpha char and there is no space \ + \between alpha and punctuation" + (fn _ => + let + (* arrange *) + val originalString = "hello!world again\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes past first alpha char when on punctuation \ + \and there is no space between punctuation and alpha" + (fn _ => + let + (* arrange *) + val originalString = "!#%&QWERTY#! hello\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "hello\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first alpha char \ + \when cursor is on space and next char is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "hello\n" + val actualString = LineGap.toString buffer + val expectedCursor = 1 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first alpha char \ + \when cursor is on space, many spaces are ahead, \ + \and first char after spaces is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "h ello\n" + val actualString = LineGap.toString buffer + val expectedCursor = 3 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first punctuation char \ + \when cursor is on space and next non-space char is punctuation" + (fn _ => + let + (* arrange *) + val originalString = "! @#$%\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dW") + + (* assert *) + val expectedString = "! @#$%\n" + val actualString = LineGap.toString buffer + val expectedCursor = 2 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dW") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dW") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 0, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello ag") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dW") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dW") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 4, finish = 6}] + val expectedNewSearchList = [{start = 4, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world test") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dW") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 15}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val deDelete = describe "delete motion 'de'" + [ test + "deletes last char and moves cursor back by one \ + \when used on last char of last word in buffer \ + \and buffer ends with a newline preceded by a non-newline" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "de") + + (* assert *) + val expectedString = "hello worl\n" + val actualString = LineGap.toString buffer + + val expectedIdx = String.size expectedString - 2 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test "deletes second word as expected when there are three words" (fn _ => + let + (* arrange *) + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "de") + val newApp2 = TestUtils.updateMany (app2, "de") + val newApp3 = TestUtils.updateMany (app3, "de") + val newApp4 = TestUtils.updateMany (app4, "de") + val newApp5 = TestUtils.updateMany (app5, "de") + + (* assert *) + val expectedString1 = "hello again\n" + val expectedString2 = "hello w again\n" + val expectedString3 = "hello wo again\n" + val expectedString4 = "hello wor again\n" + val expectedString5 = "hello worl\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + + val expectedCursor1 = 6 + val expectedCursor2 = 7 + val expectedCursor3 = 8 + val expectedCursor4 = 9 + val expectedCursor5 = 9 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes from cursor until newline when on last word \ + \before a newline and another word follows that newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "de") + + (* assert *) + val expectedString = "h\nworld\nagain\n" + (* moves cursor back by one when next char is newline *) + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first punctuation char when on an alpha char \ + \and there is no space between alpha and punctuation" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "de") + + (* assert *) + val expectedString = "!world!again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes until first alpha char when on punctuation \ + \and there is no space between punctuation and alpha" + (fn _ => + let + (* arrange *) + val originalString = "!#%&QWERTY#!\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "de") + + (* assert *) + val expectedString = "QWERTY#!\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes end of next world \ + \when cursor is on space and next char is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "de") + + (* assert *) + val expectedString = "h world\n" + val actualString = LineGap.toString buffer + val expectedCursor = 1 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes next world \ + \when cursor is on space, many spaces are ahead, \ + \and first char after spaces is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "de") + + (* assert *) + val expectedString = "h world\n" + val actualString = LineGap.toString buffer + val expectedCursor = 3 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes next word \ + \when cursor is on space and next non-space char is punctuation" + (fn _ => + let + (* arrange *) + val originalString = "! @#$% world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "de") + + (* assert *) + val expectedString = "! world\n" + val actualString = LineGap.toString buffer + val expectedCursor = 2 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "de") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "de") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 1, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello a") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "de") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello +o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "de") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 6}] + val expectedNewSearchList = [{start = 0, finish = 8}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "de") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dEdelete = describe "delete motion 'dE'" + [ test + "deletes last char and moves cursor back by one \ + \when used on last char of last word in buffer \ + \and buffer ends with a newline preceded by a non-newline" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dE") + + (* assert *) + val expectedString = "hello worl\n" + val actualString = LineGap.toString buffer + + val expectedIdx = String.size expectedString - 2 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test "deletes second word as expected when there are three words" (fn _ => + let + (* arrange *) + (* "world" is spelled with an ! instead of an l + * to make the second word fit the definition of a WORD *) + val originalString = "hello wor!d again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "dE") + val newApp2 = TestUtils.updateMany (app2, "dE") + val newApp3 = TestUtils.updateMany (app3, "dE") + val newApp4 = TestUtils.updateMany (app4, "dE") + val newApp5 = TestUtils.updateMany (app5, "dE") + + (* assert *) + val expectedString1 = "hello again\n" + val expectedString2 = "hello w again\n" + val expectedString3 = "hello wo again\n" + val expectedString4 = "hello wor again\n" + val expectedString5 = "hello wor!\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + + val expectedCursor1 = 6 + val expectedCursor2 = 7 + val expectedCursor3 = 8 + val expectedCursor4 = 9 + val expectedCursor5 = 9 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes from cursor until newline when on last word \ + \before a newline and another word follows that newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dE") + + (* assert *) + val expectedString = "h\nworld\nagain\n" + (* moves cursor back by one when next char is newline *) + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes beyond first punctuation char when on an alpha char \ + \and there is no space between alpha and punctuation" + (fn _ => + let + (* arrange *) + val originalString = "hello!world again\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dE") + + (* assert *) + val expectedString = " again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes beyond first alpha char when on punctuation \ + \and there is no space between punctuation and alpha" + (fn _ => + let + (* arrange *) + val originalString = "!#%&QWERTY#!\n" + val app = TestUtils.init originalString + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dE") + + (* assert *) + val expectedString = "\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes end of next word \ + \when cursor is on space and next char is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h e!!o world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dE") + + (* assert *) + val expectedString = "h world\n" + val actualString = LineGap.toString buffer + val expectedCursor = 1 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes next world \ + \when cursor is on space, many spaces are ahead, \ + \and first char after spaces is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h e!!o world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dE") + + (* assert *) + val expectedString = "h world\n" + val actualString = LineGap.toString buffer + val expectedCursor = 3 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes next word \ + \when cursor is on space and next non-space char is punctuation" + (fn _ => + let + (* arrange *) + val originalString = "! @#$% world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dE") + + (* assert *) + val expectedString = "! world\n" + val actualString = LineGap.toString buffer + val expectedCursor = 2 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dE") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dE") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 1, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello a") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dE") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello +o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dE") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 6}] + val expectedNewSearchList = [{start = 0, finish = 8}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dE") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dbDelete = describe "delete motion 'db'" + [ test + "deletes all characters in last word except last character \ + \when cursor is on last character of last word in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = "hello d\n" + val actualString = LineGap.toString buffer + + val expectedIdx = String.size expectedString - 2 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test "deletes second word as expected when there are three words" (fn _ => + let + (* arrange *) + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + val app6 = AppWith.idx (app, 11) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "db") + val newApp2 = TestUtils.updateMany (app2, "db") + val newApp3 = TestUtils.updateMany (app3, "db") + val newApp4 = TestUtils.updateMany (app4, "db") + val newApp5 = TestUtils.updateMany (app5, "db") + val newApp6 = TestUtils.updateMany (app6, "db") + + (* assert *) + val expectedString1 = "world again\n" + val expectedString2 = "hello orld again\n" + val expectedString3 = "hello rld again\n" + val expectedString4 = "hello ld again\n" + val expectedString5 = "hello d again\n" + val expectedString6 = "hello again\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + val actualString6 = LineGap.toString (#buffer newApp6) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + andalso expectedString6 = actualString6 + + val expectedCursor1 = 0 + val expectedCursor2 = 6 + val expectedCursor3 = 6 + val expectedCursor4 = 6 + val expectedCursor5 = 6 + val expectedCursor6 = 6 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + val actualCursor6 = #cursorIdx newApp6 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + andalso expectedCursor6 = actualCursor6 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes newline and preceding word when cursor is \ + \on first character of word that has a newline before it" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = "world\nagain\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes first punctuation char when on an alpha char \ + \which is immediately preceded by a punctuation char" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = "helloworld!again\n" + val expectedCursor = 5 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes chars until reaching punctuation when \ + \cursor is on alpha char, preceded by more alpha chars, \ + \until preceded by punctuation" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = "hello!orld!again\n" + val expectedCursor = 6 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes alpha chars when on punctuation which is immediately preceded \ + \by more alpha chars, until preceded by punctuation again" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 11) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = "hello!!again\n" + val expectedCursor = 6 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "deletes spaces and word before spaces, when cursor is on space" + (fn _ => + let + (* arrange *) + val originalString = "hello again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 13) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = " again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "does not delete when cursor is on first character of first line" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = originalString + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "db") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "db") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 1, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 12 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello ag") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "db") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 12 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello +o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "db") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 6}] + val expectedNewSearchList = [{start = 0, finish = 8}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "db") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dBDelete = describe "delete motion 'dB'" + [ test + "deletes all characters in last WORD except last character \ + \when cursor is on last character of last WORD in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dB") + + (* assert *) + val expectedString = "hello d\n" + val actualString = LineGap.toString buffer + + val expectedIdx = String.size expectedString - 2 + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test "deletes second WORD as expected when there are three WORDs" (fn _ => + let + (* arrange *) + (* 'l' in world is replaced with '!' + * so it fits with the definition of a WORD *) + val originalString = "hello wor!d again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + val app6 = AppWith.idx (app, 11) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "dB") + val newApp2 = TestUtils.updateMany (app2, "dB") + val newApp3 = TestUtils.updateMany (app3, "dB") + val newApp4 = TestUtils.updateMany (app4, "dB") + val newApp5 = TestUtils.updateMany (app5, "dB") + val newApp6 = TestUtils.updateMany (app6, "dB") + + (* assert *) + val expectedString1 = "wor!d again\n" + val expectedString2 = "hello or!d again\n" + val expectedString3 = "hello r!d again\n" + val expectedString4 = "hello !d again\n" + val expectedString5 = "hello d again\n" + val expectedString6 = "hello again\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + val actualString6 = LineGap.toString (#buffer newApp6) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + andalso expectedString6 = actualString6 + + val expectedCursor1 = 0 + val expectedCursor2 = 6 + val expectedCursor3 = 6 + val expectedCursor4 = 6 + val expectedCursor5 = 6 + val expectedCursor6 = 6 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + val actualCursor6 = #cursorIdx newApp6 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + andalso expectedCursor6 = actualCursor6 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes newline and preceding word when cursor is \ + \on first character of word that has a newline before it" + (fn _ => + let + (* arrange *) + val originalString = "he!!o\nwor!d\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dB") + + (* assert *) + val expectedString = "wor!d\nagain\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes past punctuation char when on an alpha char \ + \which is immediately preceded by a punctuation char" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dB") + + (* assert *) + val expectedString = "world!again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes chars past punctuation when \ + \cursor is on alpha char, preceded by more alpha chars, \ + \and then preceded by punctuation" + (fn _ => + let + (* arrange *) + val originalString = "he!!o!wor!d!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dB") + + (* assert *) + val expectedString = "or!d!again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes alpha and punctuation characters \ + \when string is not separated by spaces" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 11) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dB") + + (* assert *) + val expectedString = "!again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "deletes spaces and word before spaces, when cursor is on space" + (fn _ => + let + (* arrange *) + val originalString = "he!!o again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 13) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dB") + + (* assert *) + val expectedString = " again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "does not delete when cursor is on first character of first line" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "db") + + (* assert *) + val expectedString = originalString + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dB") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dB") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 1, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 12 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello ag") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dB") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 12 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello +o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dB") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 6}] + val expectedNewSearchList = [{start = 0, finish = 8}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dB") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dgeDelete = describe "delete motion 'dge'" + [ test "does not delete when cursor is at start of file" (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dge") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test "deletes as expected when on second word and there are three words" + (fn _ => + let + (* arrange *) + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "dge") + val newApp2 = TestUtils.updateMany (app2, "dge") + val newApp3 = TestUtils.updateMany (app3, "dge") + val newApp4 = TestUtils.updateMany (app4, "dge") + val newApp5 = TestUtils.updateMany (app5, "dge") + + (* assert *) + val expectedString1 = "hellorld again\n" + val expectedString2 = "hellrld again\n" + val expectedString3 = "hellld again\n" + val expectedString4 = "helld again\n" + val expectedString5 = "hell again\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + + val expectedCursor1 = 4 + val expectedCursor2 = 4 + val expectedCursor3 = 4 + val expectedCursor4 = 4 + val expectedCursor5 = 4 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes all characters in word \ + \when on last character of last word in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, String.size originalString - 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dge") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hell\n" + val expectedCursorIdx = String.size expectedString - 2 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "deletes past newline when on first word after newline" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dge") + + (* assert *) + val expectedString = "hellorld\nagain\n" + val expectedCursor = 4 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes only two chars when cursor is \ + \on an a punctuation char which is immediately preceded \ + \by an alpha char" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dge") + + (* assert *) + val expectedString = "hellworld!again\n" + val expectedCursor = 4 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes only one punctuation char when on an alpha char \ + \which is preceded by punctuation" + (fn _ => + let + (* arrange *) + val originalString = "!#%&(hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dge") + + (* assert *) + val expectedString = "!#%&lo\n" + val expectedCursor = 4 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes spaces until reaching non-space char, \ + \when cursor is on space and is preceded by more spaces" + (fn _ => + let + (* arrange *) + val originalString = "hello again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 13) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dge") + + (* assert *) + val expectedString = "hell again\n" + val expectedCursor = 4 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 15 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 4, finish = 8}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hellorld") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hella*") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 3}] + val expectedNewSearchList = [{start = 0, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dgEDelete = describe "delete motion 'dgE'" + [ test "does not delete when cursor is at start of file" (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgE") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test "deletes as expected when on second word and there are three words" + (fn _ => + let + (* arrange *) + val originalString = "hello wor!d again\n" + + val app = TestUtils.init originalString + + (* all the different positions the cursor can be + * on the second word *) + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "dgE") + val newApp2 = TestUtils.updateMany (app2, "dgE") + val newApp3 = TestUtils.updateMany (app3, "dgE") + val newApp4 = TestUtils.updateMany (app4, "dgE") + val newApp5 = TestUtils.updateMany (app5, "dgE") + + (* assert *) + val expectedString1 = "hellor!d again\n" + val expectedString2 = "hellr!d again\n" + val expectedString3 = "hell!d again\n" + val expectedString4 = "helld again\n" + val expectedString5 = "hell again\n" + + val actualString1 = LineGap.toString (#buffer newApp1) + val actualString2 = LineGap.toString (#buffer newApp2) + val actualString3 = LineGap.toString (#buffer newApp3) + val actualString4 = LineGap.toString (#buffer newApp4) + val actualString5 = LineGap.toString (#buffer newApp5) + + val stringsAreExpected = + expectedString1 = actualString1 + andalso expectedString2 = actualString2 + andalso expectedString3 = actualString3 + andalso expectedString4 = actualString4 + andalso expectedString5 = actualString5 + + val expectedCursor1 = 4 + val expectedCursor2 = 4 + val expectedCursor3 = 4 + val expectedCursor4 = 4 + val expectedCursor5 = 4 + + val actualCursor1 = #cursorIdx newApp1 + val actualCursor2 = #cursorIdx newApp2 + val actualCursor3 = #cursorIdx newApp3 + val actualCursor4 = #cursorIdx newApp4 + val actualCursor5 = #cursorIdx newApp5 + + val cursorsAreExpected = + expectedCursor1 = actualCursor1 + andalso expectedCursor2 = actualCursor2 + andalso expectedCursor3 = actualCursor3 + andalso expectedCursor4 = actualCursor4 + andalso expectedCursor5 = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes all characters in word \ + \when on last character of last word in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello wor!d\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, String.size originalString - 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgE") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hell\n" + val expectedCursorIdx = String.size expectedString - 2 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "deletes past newline when on first word after newline" (fn _ => + let + (* arrange *) + val originalString = "hello\n#orld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgE") + + (* assert *) + val expectedString = "hellorld\nagain\n" + val expectedCursor = 4 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes past punctuation \ + \when cursor is on alpha char preceded by punctuation" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgE") + + (* assert *) + val expectedString = "world!again\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes all punctuation char until beginning of string \ + \when on alpha char in first WORD, preceded by punctuation" + (fn _ => + let + (* arrange *) + val originalString = "!#%&(hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgE") + + (* assert *) + val expectedString = "lo\n" + val expectedCursor = 0 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes spaces until reaching non-space char, \ + \when cursor is on space and is preceded by more spaces" + (fn _ => + let + (* arrange *) + val originalString = "hello again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 13) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgE") + + (* assert *) + val expectedString = "hell again\n" + val expectedCursor = 4 + + val actualString = LineGap.toString buffer + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursor = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 15 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 4, finish = 8}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hellorld") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello orange oops\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hella*") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 3}] + val expectedNewSearchList = [{start = 0, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dge") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dGDelete = describe "delete motion 'dG'" + [ test + "deletes whole buffer, leaving only a newline, \ + \when cursor is on first line of buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dG") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from second line to end of buffer \ + \when first and second line only contain one newline each" + (fn _ => + let + (* arrange *) + val originalString = "\n\nhello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dG") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + + val expectedCursurIdx = 0 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursurIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test "deletes from second line onwards when cursor is on second line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dG") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "leaves preceding matches unchhanged" (fn _ => + let + (* arrange *) + val originalIdx = 12 + val originalString = "hello world\ntest again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dG") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 4}] + val expectedNewSearchList = expectedOldSearchList + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "deletes all matches on deleted line" (fn _ => + let + (* arrange *) + val originalIdx = 12 + val originalString = "alpha world\ntest again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/a") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dG") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [ {start = 0, finish = 0} + , {start = 4, finish = 4} + , {start = 17, finish = 17} + , {start = 19, finish = 19} + ] + val expectedNewSearchList = + [{start = 0, finish = 0}, {start = 4, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dggDelete = describe "delete motion 'dgg'" + [ test "leaves newline behind when deleting from last line" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgg") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes whole line that cursor is currently on, \ + \and all lines preceding cursor" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgg") + + (* assert *) + val expectedString = "again\n" + val actualString = LineGap.toString buffer + + val expectedCursurIdx = 0 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursurIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes current line and preceding lines \ + \when current line does not end with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgg") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes current line and preceding line \ + \when cursor is on a newline preceded by a newline" + (fn _ => + let + (* arrange *) + val originalString = "\n\nhello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgg") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes current line and preceding line \ + \when cursor is on a newline, followed by another newline" + (fn _ => + let + (* arrange *) + val originalString = "\n\n\nhello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dgg") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "\nhello world\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes all matches on deleted line, \ + \and decrements subsequent matches" + (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "alpha world\ntest again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/a") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dgg") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [ {start = 0, finish = 0} + , {start = 4, finish = 4} + , {start = 17, finish = 17} + , {start = 19, finish = 19} + ] + val expectedNewSearchList = + [{start = 5, finish = 5}, {start = 7, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val d0Delete = describe "delete motion 'd0'" + [ test "does not delete when on first word in buffer" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d0") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test "does not delete when cursor is on a newline" (fn _ => + let + (* arrange *) + val originalString = "hello\n\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d0") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString buffer + + val expectedCursurIdx = 6 + + val stringIsExpected = expectedString = actualString + val cursorIsExpected = expectedCursurIdx = cursorIdx + in + Expect.isTrue (stringIsExpected andalso cursorIsExpected) + end) + , test + "deletes from cursor to first character in buffer \ + \when cursor is on first line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d0") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "lo\nworld" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor to up to (not including) \ + \closest newline, when cursor is on second line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d0") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\norld\n" + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 19 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d0") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d0") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 1, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 9 + val originalString = "hello\nagain\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\nin") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d0") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\no*") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d0") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 5}] + val expectedNewSearchList = [{start = 0, finish = 6}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d0") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dDlrDelete = describe "delete motion 'd$'" + [ test + "deletes only the last character on the line \ + \and moves cursor back by one index \ + \when cursor is on last character" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 4) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d$") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hell\nworld\n" + val expectedCursorIdx = 3 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from first character in buffer \ + \up to (and excluding) the first newline \ + \when the cursor is on the first character in the buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d$") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "\nworld\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from first character in line \ + \up to (and excluding) the next newline \ + \when the cursor is on the first character of second newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d$") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\n\nagain\n" + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from middle character on line to last character in line \ + \when cursor is on middle character" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d$") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "he\nworld\n" + val expectedCursorIdx = 1 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "does not delete when cursor is on a line \ + \which contains only a single newline and nothing else" + (fn _ => + let + (* arrange *) + val originalString = "hello\n\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d$") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from middle character on last line \ + \when the last line ends with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d$") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\nw\n" + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from middle character on last line \ + \when the last line does not end with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d$") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\nw" + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 11 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d$") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d$") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 1, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nagain\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\n\nworld") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d$") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 11}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/\n+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d$") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [{start = 5, finish = 5}, {start = 11, finish = 11}] + val expectedNewSearchList = [{start = 5, finish = 6}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d$") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dCaretDelete = describe "delete motion 'd^'" + [ test + "does not delete when cursor is on first character of first line \ + \and first character is not a space" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d^") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes preceding characters when cursor is on \ + \third character of first line, and line starts with an alpha character" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d^") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "llo\nworld\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes preceding characters on line when \ + \cursor is on third character of second line \ + \and line starts with alpha character" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 8) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d^") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\nrld\n" + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from start of line until first non-space char \ + \when cursor is on first character of line \ + \and line starts with spaces" + (fn _ => + let + (* arrange *) + val originalString = " hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d^") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor position to first non-space char \ + \when cursor is on last non-space char at end of line" + (fn _ => + let + (* arrange *) + val originalString = " hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d^") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = " o\n" + val expectedCursorIdx = 3 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "does not delete from buffer or move cursor when cursor is on newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\n\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d^") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 15 + val originalString = "hello\nworld again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d^") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + + , test "decrements search list when we delete word preceding match" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d^") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 1, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 9 + val originalString = "hello\nworld\nagain\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello\nl") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d^") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 6}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 8 + val originalString = " hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/ +") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d^") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 0, finish = 2}] + val expectedNewSearchList = [{start = 0, finish = 3}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "d^") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dnDelete = describe "delete motion 'dn'" + [ test "does not delete or move cursor when there is no search" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dn") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test "does not delete when cursor is after first character of last search" + (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (** perform search **) + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dn") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 2 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "does not delete when cursor is on first character of last search" + (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (** perform search **) + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dn") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor position to first character of search \ + \when there is a search position after the cursor" + (fn _ => + let + (* arrange *) + val originalString = "hello world hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (** perform search **) + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dn") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello whello\n" + val expectedCursorIdx = 7 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "decrements matches after cursor position" (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello world hello\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dn") + + (* assert *) + val oldSearchList = PersistentVector.toList (#searchList app) + val newSearchList = PersistentVector.toList (#searchList newApp) + + val expectedOldSearchList = + [{start = 0, finish = 4}, {start = 12, finish = 16}] + val expectedNewSearchList = + [{start = 0, finish = 4}, {start = 6, finish = 10}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + (* assert *) + Expect.isTrue assertion + end) + , test "extends match when match should extend after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello hello\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/(hello)+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dn") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [{start = 0, finish = 4}, {start = 6, finish = 10}] + val expectedNewSearchList = [{start = 0, finish = 9}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "detects match when we delete in middle of it" (fn _ => + let + (* arrange *) + val originalIdx = 1 + val originalString = "hello hello hello\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dn") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [ {start = 0, finish = 4} + , {start = 6, finish = 10} + , {start = 12, finish = 16} + ] + val expectedNewSearchList = + [{start = 1, finish = 5}, {start = 7, finish = 11}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dNDelete = describe "delete motion 'dN'" + [ test "does not delete or move cursor when there is no search" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dN") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test + "does not delete when cursor is before first character of first search" + (fn _ => + let + (* arrange *) + val originalString = "world hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (** perform search **) + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dN") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 2 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "does not delete when cursor is on first character of first search" + (fn _ => + let + (* arrange *) + val originalString = "hey hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 4) + + (** perform search **) + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dN") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 4 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor position to first character of search \ + \when there is a search position before the cursor" + (fn _ => + let + (* arrange *) + val originalString = "hello world hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (** perform search **) + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dN") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "orld hello\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "decrements subsequent matches in search list \ + \when we delete prior to last match" + (fn _ => + let + (* arrange *) + val originalIdx = 1 + val originalString = "hello hello hello\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dN") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [ {start = 0, finish = 4} + , {start = 6, finish = 10} + , {start = 12, finish = 16} + ] + val expectedNewSearchList = + [{start = 5, finish = 9}, {start = 11, finish = 15}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 8 + val originalString = "hhello, ello\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dN") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 1, finish = 5}] + val expectedNewSearchList = [{start = 0, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "shortens existing match when existing match should shorten after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 7 + val originalString = "test ooooooo hi there\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dN") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 5, finish = 11}] + val expectedNewSearchList = [{start = 5, finish = 9}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 9 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dN") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [{start = 4, finish = 4}, {start = 7, finish = 7}] + val expectedNewSearchList = [{start = 4, finish = 4}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + ] + + val dfDelete = describe "delete motion 'df'" + [ test + "does not delete when there is no occurrence of \ + \after cursor position" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dff") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "does not delete when cursor is at last occurrence of in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dfw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes up to when \ + \there is an ocurrence of after cursor's position on same line" + (fn _ => + let + (* arrange *) + val originalString = "hey hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dfy") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = " hello\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes up to when the next occurrence of \ + \is after a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dfr") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "ld\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor's position to second occurrence of \ + \if motion has a count of 2" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "2dfo") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "rld\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor's position to last occurrence of \ + \if motion has a count greater than \ + \the number of occurences of , after the cursor" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "99dfl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "d\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "moves cursor back by one, when deletion range is \ + \from a newline that follows a non-newline char" + (fn _ => + let + (* arrange *) + val originalString = "hey hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dfo") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hey\n" + val expectedCursorIdx = 2 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test "leaves a newline behind if whole buffer is deleted" (fn _ => + let + (* arrange *) + val originalString = "hey hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dfo") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + ] + + val dtDelete = describe "delete motion 'dt'" + [ test + "does not delete when there is no occurrence of \ + \after cursor position" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dtf") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "does not delete when cursor is at last occurrence of in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dtw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes up to (excluding) when \ + \there is an ocurrence of after cursor's position on same line" + (fn _ => + let + (* arrange *) + val originalString = "hey hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dty") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "y hello\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes up to (excluding) when the next occurrence of \ + \is after a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dtr") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "rld\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes up to (excluding) last occurrence of chr \ + \when motion has a count greater than 1" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "2dto") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "orld\n" + val expectedCursorIdx = 0 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + ] + + val dFDelete = describe "delete motion 'dF'" + [ test + "does not delete when there is no occurrence of \ + \before cursor position" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 10) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dFq") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 10 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "does not delete when cursor is at first occurrence of in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dFw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor position to previous when \ + \there is an ocurrence of before cursor's position on same line" + (fn _ => + let + (* arrange *) + val originalString = "hey hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dFy") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "heello\n" + val expectedCursorIdx = 2 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes up to when the previous occurrence of \ + \is before a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dFl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "helorld\n" + val expectedCursorIdx = 3 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor's position to second occurrence of \ + \if motion has a count of 2" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "2dFo") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hellld\n" + val expectedCursorIdx = 4 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor's position to first occurrence of \ + \if motion has a count greater than \ + \the number of occurences of , before the cursor" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "99dFl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "heorld\n" + val expectedCursorIdx = 2 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + ] + + val dTDelete = describe "delete motion 'dT'" + [ test + "does not delete when there is no occurrence of \ + \before cursor position" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 10) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dTq") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 10 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "does not delete when cursor is at first occurrence of in buffer" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dTw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = originalString + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor position to previous (excluding) when \ + \there is an ocurrence of before cursor's position on same line" + (fn _ => + let + (* arrange *) + val originalString = "hey hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dTy") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "heyello\n" + val expectedCursorIdx = 3 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes up to (excluding) when the previous occurrence of \ + \is before a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dTl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hellorld\n" + val expectedCursorIdx = 4 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor's position to second occurrence of \ + \(excluding) if motion has a count of 2" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "2dTo") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hellold\n" + val expectedCursorIdx = 5 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes from cursor's position to first occurrence of \ + \(excluding) if motion has a count greater than \ + \the number of occurences of , before the cursor" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "99dTl") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "helorld\n" + val expectedCursorIdx = 3 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + ] + + val diwDelete = describe "delete motion 'diw' (delete inside word)" + [ test + "deletes middle word when middle word is \ + \an alphanumeric word surrounded on both sides by spaces" + (fn _ => + let + (* arrange *) + val originalString = "hello abc_123 world\n" + val app = TestUtils.init originalString + + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + val app6 = AppWith.idx (app, 11) + val app7 = AppWith.idx (app, 12) + + (* act *) + val app1 = TestUtils.updateMany (app1, "diw") + val app2 = TestUtils.updateMany (app2, "diw") + val app3 = TestUtils.updateMany (app3, "diw") + val app4 = TestUtils.updateMany (app4, "diw") + val app5 = TestUtils.updateMany (app5, "diw") + val app6 = TestUtils.updateMany (app6, "diw") + val app7 = TestUtils.updateMany (app7, "diw") + + (* assert *) + val expectedString = "hello world\n" + val expectedCursorIdx = 6 + + val actualString1 = LineGap.toString (#buffer app1) + val actualString2 = LineGap.toString (#buffer app2) + val actualString3 = LineGap.toString (#buffer app3) + val actualString4 = LineGap.toString (#buffer app4) + val actualString5 = LineGap.toString (#buffer app5) + val actualString6 = LineGap.toString (#buffer app6) + val actualString7 = LineGap.toString (#buffer app7) + + val stringsAreExpected = + actualString1 = expectedString + andalso actualString2 = expectedString + andalso actualString3 = expectedString + andalso actualString4 = expectedString + andalso actualString5 = expectedString + andalso actualString6 = expectedString + andalso actualString7 = expectedString + + val actualCursor1 = #cursorIdx app1 + val actualCursor2 = #cursorIdx app2 + val actualCursor3 = #cursorIdx app3 + val actualCursor4 = #cursorIdx app4 + val actualCursor5 = #cursorIdx app5 + val actualCursor6 = #cursorIdx app6 + val actualCursor7 = #cursorIdx app7 + + val cursorsAreExpected = + actualCursor1 = expectedCursorIdx + andalso actualCursor2 = expectedCursorIdx + andalso actualCursor3 = expectedCursorIdx + andalso actualCursor4 = expectedCursorIdx + andalso actualCursor5 = expectedCursorIdx + andalso actualCursor6 = expectedCursorIdx + andalso actualCursor7 = expectedCursorIdx + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes middle word if word is a punctuation word \ + \surrounded by spaces" + (fn _ => + let + (* arrange *) + val originalString = "hello !#%&~ world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "does not delete punctuation char to the left \ + \if middle word to be deleted is an alpha word" + (fn _ => + let + (* arrange *) + val originalString = "hello !good world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello ! world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "does not delete alpha char to the left \ + \if middle word to be deleted is a punctuation word" + (fn _ => + let + (* arrange *) + val originalString = "hello a#%!& world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello a world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "does not delete punctuation char to the right \ + \if middle word to be deleted is an alpha word" + (fn _ => + let + (* arrange *) + val originalString = "hello good# world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello # world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "does not delete alpha char to the right \ + \if middle word to be deleted is a punctuation word" + (fn _ => + let + (* arrange *) + val originalString = "hello #%!&z world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello z world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "does not delete punctuation chars to the left and right \ + \if middle word to be deleted is an alpha word" + (fn _ => + let + (* arrange *) + val originalString = "hello (zoo) world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello () world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "does not delete alpha chars to the left and right \ + \if middle word to be deleted is a punctuation word" + (fn _ => + let + (* arrange *) + val originalString = "hello a#%&~z world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello az world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes contiguous spaces when cursor is on space \ + \which is between the middle of two words" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diw") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "helloworld\n" + in + Expect.isTrue (actualString = expectedString) + end) + ] + + val diWDelete = describe "delete motion 'diW' (delete inside WORD)" + [ test + "deletes middle word when middle word is \ + \an alphanumeric word surrounded on both sides by spaces" + (fn _ => + let + (* arrange *) + val originalString = "hello &bc_12# world\n" + val app = TestUtils.init originalString + + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + val app6 = AppWith.idx (app, 11) + val app7 = AppWith.idx (app, 12) + + (* act *) + val app1 = TestUtils.updateMany (app1, "diW") + val app2 = TestUtils.updateMany (app2, "diW") + val app3 = TestUtils.updateMany (app3, "diW") + val app4 = TestUtils.updateMany (app4, "diW") + val app5 = TestUtils.updateMany (app5, "diW") + val app6 = TestUtils.updateMany (app6, "diW") + val app7 = TestUtils.updateMany (app7, "diW") + + (* assert *) + val expectedString = "hello world\n" + val expectedCursorIdx = 6 + + val actualString1 = LineGap.toString (#buffer app1) + val actualString2 = LineGap.toString (#buffer app2) + val actualString3 = LineGap.toString (#buffer app3) + val actualString4 = LineGap.toString (#buffer app4) + val actualString5 = LineGap.toString (#buffer app5) + val actualString6 = LineGap.toString (#buffer app6) + val actualString7 = LineGap.toString (#buffer app7) + + val stringsAreExpected = + actualString1 = expectedString + andalso actualString2 = expectedString + andalso actualString3 = expectedString + andalso actualString4 = expectedString + andalso actualString5 = expectedString + andalso actualString6 = expectedString + andalso actualString7 = expectedString + + val actualCursor1 = #cursorIdx app1 + val actualCursor2 = #cursorIdx app2 + val actualCursor3 = #cursorIdx app3 + val actualCursor4 = #cursorIdx app4 + val actualCursor5 = #cursorIdx app5 + val actualCursor6 = #cursorIdx app6 + val actualCursor7 = #cursorIdx app7 + + val cursorsAreExpected = + actualCursor1 = expectedCursorIdx + andalso actualCursor2 = expectedCursorIdx + andalso actualCursor3 = expectedCursorIdx + andalso actualCursor4 = expectedCursorIdx + andalso actualCursor5 = expectedCursorIdx + andalso actualCursor6 = expectedCursorIdx + andalso actualCursor7 = expectedCursorIdx + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes middle word if word is a punctuation word \ + \surrounded by spaces" + (fn _ => + let + (* arrange *) + val originalString = "hello !#%&~ world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes middle WORD when on alpha character, \ + \including punctuation char on the left" + (fn _ => + let + (* arrange *) + val originalString = "hello !good world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes middle WORD when on punctuation character, \ + \including alpha char on the left" + (fn _ => + let + (* arrange *) + val originalString = "hello a#%!& world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes middle WORD when on alpha char, \ + \including punctuation to the right" + (fn _ => + let + (* arrange *) + val originalString = "hello good# world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes middle WORD when on punctuation char, \ + \including alpha char to the right" + (fn _ => + let + (* arrange *) + val originalString = "hello #%!&z world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes middle WORD, including surrounding punctuation \ + \when cursor is on alpha char" + (fn _ => + let + (* arrange *) + val originalString = "hello (zoo) world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes middle WORD, including surrounding alpha chars \ + \when cursor is on punctuation char" + (fn _ => + let + (* arrange *) + val originalString = "hello a#%&~z world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "hello world\n" + in + Expect.isTrue (actualString = expectedString) + end) + , test + "deletes contiguous spaces when cursor is on space \ + \which is between the middle of two words" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "diW") + + (* assert *) + val actualString = LineGap.toString buffer + val expectedString = "helloworld\n" + in + Expect.isTrue (actualString = expectedString) + end) + ] + + val dawDelete = describe "delete motion 'daw' (delete around word)" + [ test "deletes a single newline when cursor is on a newline" (fn _ => + let + (* arrange *) + val originalString = "hello\n\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daw") + + (* assert *) + val expectedString = "hello\nworld\n" + val actualString = LineGap.toString buffer + + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes middle word and spaces after middle word \ + \when there are three words on line and cursor is on middle word" + (fn _ => + let + (* arrange *) + val originalString = "hello world again\n" + val app = TestUtils.init originalString + + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val app1 = TestUtils.updateMany (app1, "daw") + val app2 = TestUtils.updateMany (app2, "daw") + val app3 = TestUtils.updateMany (app3, "daw") + val app4 = TestUtils.updateMany (app4, "daw") + val app5 = TestUtils.updateMany (app5, "daw") + + (* assert *) + val expectedString = "hello again\n" + + val actualString1 = LineGap.toString (#buffer app1) + val actualString2 = LineGap.toString (#buffer app2) + val actualString3 = LineGap.toString (#buffer app3) + val actualString4 = LineGap.toString (#buffer app4) + val actualString5 = LineGap.toString (#buffer app5) + + val stringsAreExpected = + expectedString = actualString1 + andalso expectedString = actualString2 + andalso expectedString = actualString3 + andalso expectedString = actualString4 + andalso expectedString = actualString5 + + val expectedCursorIdx = 6 + + val actualCursor1 = #cursorIdx app1 + val actualCursor2 = #cursorIdx app2 + val actualCursor3 = #cursorIdx app3 + val actualCursor4 = #cursorIdx app4 + val actualCursor5 = #cursorIdx app5 + + val cursorsAreExpected = + expectedCursorIdx = actualCursor1 + andalso expectedCursorIdx = actualCursor2 + andalso expectedCursorIdx = actualCursor3 + andalso expectedCursorIdx = actualCursor4 + andalso expectedCursorIdx = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes trailing spaces when cursor is on word \ + \that has multiple trailing spaces after it" + (fn _ => + let + (* arrange *) + val originalString = "hello again world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daw") + + (* assert *) + val expectedString = "hello world\n" + val actualString = LineGap.toString buffer + + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes last word on line when cursor is on space \ + \which immediately precedes last word" + (fn _ => + let + (* arrange *) + val originalString = "hello again world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 13) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daw") + + (* assert *) + val expectedString = "hello again\n" + val actualString = LineGap.toString buffer + + val expectedCursorIdx = 10 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes alpha word when cursor is on alpha char \ + \and alpha word is surrounded by punctuation chars" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "#hello#\n" + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daw") + + (* assert *) + val expectedIdx = 1 + val expectedString = "##\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes punctuation word when cursor is on punctuation char \ + \and punctuation word is surrounded by alpha chars" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "h#%&(e\n" + val app = AppWith.idx (app, 3) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daw") + + (* assert *) + val expectedIdx = 1 + val expectedString = "he\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + ] + + val daWDelete = describe "delete motion 'daW' (delete around word)" + [ test "deletes a single newline when cursor is on a newline" (fn _ => + let + (* arrange *) + val originalString = "hello\n\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daW") + + (* assert *) + val expectedString = "hello\nworld\n" + val actualString = LineGap.toString buffer + + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes middle WORD and spaces after middle WORD \ + \when there are three WORDS on line and cursor is on middle WORD" + (fn _ => + let + (* arrange *) + val originalString = "he!!o wor!d aga!n\n" + val app = TestUtils.init originalString + + val app1 = AppWith.idx (app, 6) + val app2 = AppWith.idx (app, 7) + val app3 = AppWith.idx (app, 8) + val app4 = AppWith.idx (app, 9) + val app5 = AppWith.idx (app, 10) + + (* act *) + val app1 = TestUtils.updateMany (app1, "daW") + val app2 = TestUtils.updateMany (app2, "daW") + val app3 = TestUtils.updateMany (app3, "daW") + val app4 = TestUtils.updateMany (app4, "daW") + val app5 = TestUtils.updateMany (app5, "daW") + + (* assert *) + val expectedString = "he!!o aga!n\n" + + val actualString1 = LineGap.toString (#buffer app1) + val actualString2 = LineGap.toString (#buffer app2) + val actualString3 = LineGap.toString (#buffer app3) + val actualString4 = LineGap.toString (#buffer app4) + val actualString5 = LineGap.toString (#buffer app5) + + val stringsAreExpected = + expectedString = actualString1 + andalso expectedString = actualString2 + andalso expectedString = actualString3 + andalso expectedString = actualString4 + andalso expectedString = actualString5 + + val expectedCursorIdx = 6 + + val actualCursor1 = #cursorIdx app1 + val actualCursor2 = #cursorIdx app2 + val actualCursor3 = #cursorIdx app3 + val actualCursor4 = #cursorIdx app4 + val actualCursor5 = #cursorIdx app5 + + val cursorsAreExpected = + expectedCursorIdx = actualCursor1 + andalso expectedCursorIdx = actualCursor2 + andalso expectedCursorIdx = actualCursor3 + andalso expectedCursorIdx = actualCursor4 + andalso expectedCursorIdx = actualCursor5 + in + Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) + end) + , test + "deletes trailing spaces when cursor is on WORD \ + \that has multiple trailing spaces after it" + (fn _ => + let + (* arrange *) + val originalString = "hello a&a!n world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daW") + + (* assert *) + val expectedString = "hello world\n" + val actualString = LineGap.toString buffer + + val expectedCursorIdx = 6 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes last WORD on line when cursor is on space \ + \which immediately precedes last WORD" + (fn _ => + let + (* arrange *) + val originalString = "hello again w()r!d\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 13) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daW") + + (* assert *) + val expectedString = "hello again\n" + val actualString = LineGap.toString buffer + + val expectedCursorIdx = 10 + in + Expect.isTrue + (actualString = expectedString + andalso cursorIdx = expectedCursorIdx) + end) + , test + "deletes WORD, including surrounding punctuation, \ + \when cursor is on alpha char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello a^a!n world\n" + val app = AppWith.idx (app, 8) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daW") + + (* assert *) + val expectedIdx = 6 + val expectedString = "hello world\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + , test + "deletes WORD, including surrounding alpha chars, \ + \when cursor is on punctuation char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello a!#%&(z world\n" + val app = AppWith.idx (app, 8) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "daW") + + (* assert *) + val expectedIdx = 6 + val expectedString = "hello world\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue + (expectedString = actualString andalso expectedIdx = cursorIdx) + end) + ] + + val pairDelete = describe "delete motion 'd%' (delete pair)" + [ test + "deletes from cursor to matching ) \ + \when cursor is on (" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "(hello)\n" + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + val expectedIdx = 0 + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test + "deletes from cursor to matching ( \ + \when cursor is on )" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "(hello)\n" + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + (* testing that cursor deletes at correct level of nesting *) + , test "deletes to outermouse ) when cursor is on outermost (" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to outermost ( when cursor is on outermost )" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 10) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to middle ) when cursor is on middle (" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "()\n" + val expectedIdx = 1 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to middle ( when cursor is on middle )" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "()\n" + val expectedIdx = 1 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to innermost ) when cursor is on innermost (" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 2) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "(())\n" + val expectedIdx = 2 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to innermost ( when cursor is on innermost )" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 8) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "(())\n" + val expectedIdx = 2 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + (* testing different pair combinations *) + , test "deletes to next ] when cursor is on [" (fn _ => + let + (* arrange *) + val app = TestUtils.init "[hello]\n" + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to preceding [ when cursur is on ]" (fn _ => + let + (* arrange *) + val app = TestUtils.init "[hello]\n" + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to next } when cursor is on {" (fn _ => + let + (* arrange *) + val app = TestUtils.init "{hello}\n" + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to preceding { when cursur is on }" (fn _ => + let + (* arrange *) + val app = TestUtils.init "{hello}\n" + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to next > when cursor is on <" (fn _ => + let + (* arrange *) + val app = TestUtils.init "\n" + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test "deletes to preceding < when cursur is on >" (fn _ => + let + (* arrange *) + val app = TestUtils.init "\n" + val app = AppWith.idx (app, 6) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + , test + "does not delete when cursor is on a non-pair-character, \ + \and there is no pair-character where the cursor is at or after the cursor" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "he()o, world\n" + val app = AppWith.idx (app, 5) + val oldBuffer = #buffer app + val oldIdx = #cursorIdx app + + (* act *) + val app = TestUtils.updateMany (app, "d%") + val newBuffer = #buffer app + val newIdx = #cursorIdx app + + (* assert *) + val oldString = LineGap.toString oldBuffer + val newString = LineGap.toString newBuffer + in + (* assert *) + Expect.isTrue (newString = oldString andalso newIdx = oldIdx) + end) + , test + "deletes from cursor to matching pair \ + \when cursor is not on a pair-character \ + \but there is a pair-character after it" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "he()o world\n" + val app = AppWith.idx (app, 0) + + (* act *) + val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "d%") + + (* assert *) + val expectedString = "o world\n" + val expectedIdx = 0 + val actualString = LineGap.toString buffer + in + (* assert *) + Expect.isTrue + (actualString = expectedString andalso cursorIdx = expectedIdx) + end) + ] + + val diParenDelete = describe "delete motion 'di('" + [ test "does not delete when there is no ( after the cursor" (fn _ => + let + (* arrange *) + val originalString = " ( ) hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "di(") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + , test + "deletes pair after cursor when there is a pair \ + \before the cursor and after the cursor" + (fn _ => + let + (* arrange *) + val originalString = " ( abc ) xyz ( def )\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "di(") + + (* assert *) + val expectedString = " ( abc ) xyz ()\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + , test + "deletes inside outer parens when cursor is in \ + \outer paren, and there is an inner paren after cursor" + (fn _ => + let + (* arrange *) + val originalString = "( ( hello ) )\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "di(") + + (* assert *) + val expectedString = "()\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + , test + "deletes inside inner parren when cursor is in inner paren-pair, \ + \and there is an outer paren above this inner paren" + (fn _ => + let + (* arrange *) + val originalString = "( ( hello ) )\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "di(") + + (* assert *) + val expectedString = "( () )\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + ] + + val daParenDelete = describe "delete motion 'da('" + [ test "does not delete when there is no ( after the cursor" (fn _ => + let + (* arrange *) + val originalString = " ( ) hello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 7) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "da(") + + (* assert *) + val expectedString = originalString + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + , test + "deletes pair after cursor when there is a pair \ + \before the cursor and after the cursor" + (fn _ => + let + (* arrange *) + val originalString = " ( abc ) xyz ( def )\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 9) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "da(") + + (* assert *) + val expectedString = " ( abc ) xyz \n" + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + , test + "deletes outer parens when cursor is in \ + \outer paren, and there is an inner paren after cursor" + (fn _ => + let + (* arrange *) + val originalString = "( ( hello ) )\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "da(") + + (* assert *) + val expectedString = "\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + , test + "deletes inner parren when cursor is in inner paren-pair, \ + \and there is an outer paren above this inner paren" + (fn _ => + let + (* arrange *) + val originalString = "( ( hello ) )\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val {buffer, ...} = TestUtils.updateMany (app, "da(") + + (* assert *) + val expectedString = "( )\n" + val actualString = LineGap.toString buffer + in + Expect.isTrue (expectedString = actualString) + end) + ] + + val tests = + [ dhDelete + , dlDelete + , djDelete + , ddDelete + , dkDelete + , dwDelete + , dWDelete + , deDelete + , dEdelete + , dbDelete + , dBDelete + , dgeDelete + , dgEDelete + , dGDelete + , dggDelete + , d0Delete + , dDlrDelete + , dCaretDelete + , dnDelete + , dNDelete + , dfDelete + , dtDelete + , dFDelete + , dTDelete + , diwDelete + , diWDelete + , dawDelete + , daWDelete + , pairDelete + , diParenDelete + , daParenDelete + ] +end diff --git a/shf/test/normal-move-tests.sml b/shf/test/normal-move-tests.sml new file mode 100644 index 0000000..52d5e3b --- /dev/null +++ b/shf/test/normal-move-tests.sml @@ -0,0 +1,2143 @@ +structure NormalMoveTests = +struct + open Railroad + open Railroad.Test + open InputMsg + + fun getChr (app: AppType.app_type) = + let + val {cursorIdx, buffer, ...} = app + val c = LineGap.substring (cursorIdx, 1, buffer) + in + String.sub (c, 0) + end + + val hMove = describe "move motion 'h'" + [ test "moves cursor left by one when cursorIdx > 0 and is not on a newline" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 1) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"h") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test "does not move cursor when cursor is already at index 0" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\nworld\n" + val app = AppWith.idx (app, 0) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"h") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test + "moves cursor to char before a newline\ + \ when there is just one newline to the left" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\nworld\n" + val app = AppWith.idx (app, 6) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"h") + in + (* assert *) + Expect.isTrue (cursorIdx = 4) + end) + , test + "moves cursor past first newline immediately following \ + \a non-newline character" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\nworld\n" + val app = AppWith.idx (app, 7) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"h") + in + (* assert *) + Expect.isTrue (cursorIdx = 6) + end) + , test + "moves cursor past newline when we see, to the left, \ + \ a newline with a chr prior to it" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\nworld\n" + val app = AppWith.idx (app, 6) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"h") + in + (* assert *) + Expect.isTrue (cursorIdx = 4) + end) + , test "moves cursor to a newline when newline is not preceded by char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "\n\n\nhello\n" + val app = AppWith.idx (app, 3) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"h") + val app2 = TestUtils.update (app1, CHAR_EVENT #"h") + val app3 = TestUtils.update (app2, CHAR_EVENT #"h") + val app4 = TestUtils.update (app3, CHAR_EVENT #"h") + + (* assert *) + val c1 = #cursorIdx app1 = 2 + val c2 = #cursorIdx app2 = 1 + val c3 = #cursorIdx app3 = 0 + val c4 = #cursorIdx app4 = 0 + in + Expect.isTrue (c1 andalso c2 andalso c3 andalso c4) + end) + ] + + val lMove = describe "move motion 'l'" + [ test "moves cursor right by one when cursorIdx < length" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val {cursorIdx = oldCursorIdx, ...} = app + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"l") + in + (* assert *) + Expect.isTrue (oldCursorIdx = 0 andalso cursorIdx = 1) + end) + , test + "does not move cursor when cursorIdx is at end of buffer \ + \and last char is a newline preceded by a newline" + (fn _ => + let + (* arrange *) + val str = "hello world\n\n" + val initialCursorIdx = String.size str - 1 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"l") + in + (* assert *) + Expect.isTrue (cursorIdx = initialCursorIdx) + end) + , test + "does not move cursor when cursorIdx is at end of buffer \ + \and last char is a non-newline preceded by a non-newline" + (fn _ => + let + (* arrange *) + val str = "hello world" + val initialCursorIdx = String.size str - 1 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"l") + in + (* assert *) + Expect.isTrue (cursorIdx = initialCursorIdx) + end) + , test + "does not move cursor when cursorIdx is at end of buffer \ + \and last char is a newline preceded by a non-newline" + (fn _ => + let + (* arrange *) + val str = "hello world\n" + val initialCursorIdx = String.size str - 2 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"l") + in + (* assert *) + Expect.isTrue (cursorIdx = initialCursorIdx) + end) + , test "moves cursor to char past newline when newline is preceded by char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\nworld\n" + val app = AppWith.idx (app, 4) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"l") + in + (* assert *) + Expect.isTrue (cursorIdx = 6) + end) + , test "moves cursor to second newline when newline is preceded by char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\nworld\n" + val app = AppWith.idx (app, 4) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"l") + in + (* assert *) + Expect.isTrue (cursorIdx = 6) + end) + , test + "moves cursor to each newline without skipping when no newline \ + \is preceded by char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "\n\n\nhello\n" + val app = AppWith.idx (app, 0) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"l") + val app2 = TestUtils.update (app1, CHAR_EVENT #"l") + val app3 = TestUtils.update (app2, CHAR_EVENT #"l") + val app4 = TestUtils.update (app3, CHAR_EVENT #"l") + + (* assert *) + val c1 = #cursorIdx app1 = 1 + val c2 = #cursorIdx app2 = 2 + val c3 = #cursorIdx app3 = 3 + val c4 = #cursorIdx app4 = 4 + in + Expect.isTrue (c1 andalso c2 andalso c3 andalso c4) + end) + ] + + val jMove = describe "move motion 'j'" + [ test "moves cursur down one column when column = 0" (fn _ => + let + (* arrange *) + (* "world" at end of string is intentionally misspelled as "qorld" + * since "world" appears twice and it is useful to differentiate them + * *) + val app = TestUtils.init "hello \nworld \ngoodbye \nqorld \n" + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"j") + val app2 = TestUtils.update (app1, CHAR_EVENT #"j") + val app3 = TestUtils.update (app2, CHAR_EVENT #"j") + + (* assert *) + val c1 = getChr app1 = #"w" + val c2 = getChr app2 = #"g" + val c3 = getChr app3 = #"q" + in + Expect.isTrue (c1 andalso c2 andalso c3) + end) + , test "moves cursur down one column when column = 1" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello \nworld \nbye \nfriends \n" + val app = AppWith.idx (app, 1) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"j") + val app2 = TestUtils.update (app1, CHAR_EVENT #"j") + val app3 = TestUtils.update (app2, CHAR_EVENT #"j") + + (* assert *) + val c1 = getChr app1 = #"o" + val c2 = getChr app2 = #"y" + val c3 = getChr app3 = #"r" + in + Expect.isTrue (c1 andalso c2 andalso c3) + end) + , test "moves cursur down one column when column = 2" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello \nworld \nbye \nfriends \n" + val app = AppWith.idx (app, 2) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"j") + val app2 = TestUtils.update (app1, CHAR_EVENT #"j") + val app3 = TestUtils.update (app2, CHAR_EVENT #"j") + + (* assert *) + val c1 = getChr app1 = #"r" + val c2 = getChr app2 = #"e" + val c3 = getChr app3 = #"i" + in + Expect.isTrue (c1 andalso c2 andalso c3) + end) + , test + "moves to last char on below column \ + \when cursor is on a column that is greater than \ + \the number of columns on the next line" + (fn _ => + let + (* arrange *) + val str = + "hello world!\n\ + \bye!\n" + val app = TestUtils.init str + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"j") + + (* assert *) + val c1 = getChr app = #"!" + in + Expect.isTrue c1 + end) + , test "when next newline is preceded by char, goes to idx after newline" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\nworld\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"j") + in + (* assert *) + Expect.isTrue (cursorIdx = 6) + end) + , test "moves to same column on last line after a count" (fn _ => + let + (* arrange *) + val str = + "let\n\ + \hello\n\ + \in\n\ + \0\n\ + \end\n" + + val app = TestUtils.init str + val app1 = AppWith.idx (app, 0) + val app2 = AppWith.idx (app, 1) + val app3 = AppWith.idx (app, 2) + + (* act *) + val newApp1 = TestUtils.updateMany (app1, "4j") + val newApp2 = TestUtils.updateMany (app2, "4j") + val newApp3 = TestUtils.updateMany (app3, "4j") + + (* assert *) + val c1 = getChr newApp1 = #"e" + val c2 = getChr newApp2 = #"n" + val c3 = getChr newApp3 = #"d" + in + Expect.isTrue (c1 andalso c2 andalso c3) + end) + , test "leaves cursor at same idx when on the last line" (fn _ => + let + (* arrange *) + val str = "hello \nworld \ntime to go\n\n" + val app = TestUtils.init str + + val initialCursorIdx = String.size str - 1 + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"j") + in + (* assert *) + Expect.isTrue (cursorIdx = initialCursorIdx) + end) + , test "goes to next idx when cursor is on a newline" (fn _ => + let + (* arrange *) + val str = "hello\n\nworld\n" + val app = TestUtils.init str + val app = AppWith.idx (app, 6) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"j") + in + (* assert *) + Expect.isTrue (cursorIdx = 7) + end) + , test + "goes to second-last newline in file \ + \when newline is preceded by a non-newline" + (fn _ => + let + (* arrange *) + val str = "hello\nworld\n" + val initialCursorIdx = 4 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"j") + + (* assert *) + val expectedIdx = 10 + in + Expect.isTrue (cursorIdx = expectedIdx) + end) + , test + "goes to last newline in file \ + \when newline is preceded by another newline" + (fn _ => + let + (* arrange *) + val str = "hello\n\nworld\n\n" + val initialCursorIdx = String.size str - 5 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"j") + + (* assert *) + val expectedIdx = String.size str - 1 + in + Expect.isTrue (cursorIdx = expectedIdx) + end) + , test "goes to last line in file when last char is not a newline" (fn _ => + let + (* arrange *) + val str = "hello\nworld" + val initialCursorIdx = 0 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"j") + + (* assert *) + val expectedIdx = 6 + in + Expect.isTrue (cursorIdx = expectedIdx) + end) + , test + "leaves cursor at same idx when on last line \ + \and file ends with a non-newline" + (fn _ => + let + (* arrange *) + val str = "hello\nworld" + val initialCursorIdx = 6 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"j") + + (* assert *) + val expectedIdx = initialCursorIdx + in + Expect.isTrue (cursorIdx = expectedIdx) + end) + , test + "does not go to last chr in file \ + \when last chr is a newline preceded by a non-newline \ + \and a count is provided" + (fn _ => + let + (* arrange *) + val str = "hello\nworld\n" + val initialCursorIdx = 0 + + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.updateMany (app, "2j") + + (* assert *) + val expectedIdx = 6 + in + Expect.isTrue (cursorIdx = expectedIdx) + end) + ] + + val kMove = describe "move motion 'k'" + [ test "moves cursur up one column when column = 0" (fn _ => + let + (* arrange *) + val app = TestUtils.init "0__\n4___\n9___\n14_\n" + val app = AppWith.idx (app, 14) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"k") + val app2 = TestUtils.update (app1, CHAR_EVENT #"k") + val app3 = TestUtils.update (app2, CHAR_EVENT #"k") + + (* assert *) + val c1 = getChr app1 = #"9" + val c2 = getChr app2 = #"4" + val c3 = getChr app3 = #"0" + in + Expect.isTrue (c1 andalso c2 andalso c3) + end) + , test "moves cursur up one column when column = 1" (fn _ => + let + (* arrange *) + val app = TestUtils.init "_w_\n_5__\n_10_\n_15\n" + val app = AppWith.idx (app, 15) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"k") + val app2 = TestUtils.update (app1, CHAR_EVENT #"k") + val app3 = TestUtils.update (app2, CHAR_EVENT #"k") + + (* assert *) + val c1 = getChr app1 = #"1" + val c2 = getChr app2 = #"5" + val c3 = getChr app3 = #"w" + in + Expect.isTrue (c1 andalso c2 andalso c3) + end) + , test "moves cursur up one column when column = 2" (fn _ => + let + (* arrange *) + val app = TestUtils.init "__2\n__6\n__10\n__15\n" + val app = AppWith.idx (app, 15) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"k") + val app2 = TestUtils.update (app1, CHAR_EVENT #"k") + val app3 = TestUtils.update (app2, CHAR_EVENT #"k") + + (* assert *) + val c1 = getChr app1 = #"1" + val c2 = getChr app2 = #"6" + val c3 = getChr app3 = #"2" + in + Expect.isTrue (c1 andalso c2 andalso c3) + end) + , test "goes to last newline when there are two newlines preceding cursor" + (fn _ => + let + (* arrange *) + val str = "hello\n\n world\n" + val app = TestUtils.init str + val app = AppWith.idx (app, 7) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"k") + in + (* assert *) + Expect.isTrue (cursorIdx = 6) + end) + , test "leaves cursor at same idx when already on first line" (fn _ => + let + (* arrange *) + val str = "hello \nworld \ntime to go\n" + val app = TestUtils.init str + (* line below does nothing; just for explicitness *) + val app = AppWith.idx (app, 0) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"k") + + (* assert *) + val isAtStart = cursorIdx = 0 + in + Expect.isTrue isAtStart + end) + , test + "goes to last column of previous line when cursor is \ + \on a column greater than the number of columns in the previous line" + (fn _ => + let + (* arrange *) + val str = + "hello world\n\ + \now a quite long line is next\n" + + val app = TestUtils.init str + val app = AppWith.idx (app, String.size str - 2) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"k") + in + (* assert *) + Expect.isTrue (cursorIdx = 10) + end) + , test + "when the previous newline is preceded by a non-newline, \ + \jumps past newline" + (fn _ => + let + (* arrange *) + val str = "hello\n\nworld\n" + + val app = TestUtils.init str + val app = AppWith.idx (app, 6) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"k") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test + "when file ends with two newlines, \ + \and cursor is on second-last newline, \ + \we should be able to move up by one line" + (fn _ => + let + (* arrange *) + val str = "hello\nworld\n\n" + + val app = TestUtils.init str + val app = AppWith.idx (app, 12) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"k") + in + (* assert *) + Expect.isTrue (cursorIdx = 6) + end) + ] + + val wMove = describe "move motion 'w'" + [ test "moves cursor to start of next word in contiguous string" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val chr = String.sub ("hello world", cursorIdx) + in + Expect.isTrue (chr = #"w") + end) + , test + "moves cursor to first char-after-newline \ + \when cursor is on last word of line \ + \and there is another line after this one" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello \n\n\n world\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"w") + in + (* assert *) + Expect.isTrue (cursorIdx = 7) + end) + , test "does not break on undescore when cursor is on alphanumeric char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello_world goodbye_world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"g") + end) + , test "breaks on punctuation when cursor is on alphanumeric char" (fn _ => + (* vi's definition of 'word' instead of 'WORD' *) + let + (* arrange *) + val app = TestUtils.init "hello, world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #",") + end) + , test "breaks on alphanumeric char when cursor is on punctuation" (fn _ => + (* vi's definition of 'word' instead of 'WORD' *) + let + (* arrange *) + val app = TestUtils.init "!#%^()hello\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"h") + end) + , test "breaks on non-blank char when on blank char" (fn _ => + let + (* arrange *) + val app = TestUtils.init "0123 \t \n \t 789\n" + val app = AppWith.idx (app, 4) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"7") + end) + , test "moves cursor to first alphanumeric char when on punctuation" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "!!! hello\n" + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val startsAtExc = getChr app = #"!" + val movedToH = getChr app1 = #"h" + in + Expect.isTrue (startsAtExc andalso movedToH) + end) + , test "moves cursor to last char when cursor is on last word" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val chrIsEnd = getChr app = #"d" + in + Expect.isTrue chrIsEnd + end) + , test + "moves cursor to second newline when cursor is on the last word \ + \and the file ends with two newlines" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\n" + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + in + (* assert *) + Expect.isTrue (#cursorIdx app = 6) + end) + , test + "does not move to or past newline when cursor is on last word \ + \and text ends with newline" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n" + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + in + (* assert *) + Expect.isTrue (#cursorIdx app = 4) + end) + , test "moves cursor to character after newline" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\nworld\nagain\n" + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + in + (* assert *) + Expect.isTrue (#cursorIdx app = 6) + end) + ] + + val WMove = describe "move motion 'W'" + [ test "moves cursor to start of next WORD in contiguous string" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"W") + + (* assert *) + val chr = String.sub ("hello world", cursorIdx) + in + Expect.isTrue (chr = #"w") + end) + , test + "moves cursor to first char-after-newline \ + \when cursor is on last word of line \ + \and there is another line after this one" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello \n\n\n world\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"W") + in + (* assert *) + Expect.isTrue (cursorIdx = 7) + end) + , test "does not break on punctuation when cursor is on alphanumeric char" + (fn _ => + (* vi's definition of 'WORD' instead of 'word' *) + let + (* arrange *) + val app = TestUtils.init "hello, world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"W") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"w") + end) + , test "does not break on alphanumeric char when cursor is on punctuation" + (fn _ => + (* vi's definition of 'WORD' instead of 'word' *) + let + (* arrange *) + val app = TestUtils.init "#!hello!!! world!!!\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"W") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"w") + end) + , test "moves cursor to first non-blank when cursor is on blank" (fn _ => + let + (* arrange *) + val app = TestUtils.init "0123 \t \n \t 789\n" + val app = AppWith.idx (app, 4) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"7") + end) + , test "moves cursor to last char when cursor is on last word" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"w") + + (* assert *) + val chrIsEnd = getChr app = #"d" + in + Expect.isTrue chrIsEnd + end) + ] + + val eMove = describe "move motion 'e'" + [ test + "moves cursor to last alphanumeric char in contiguous string\ + \when in alphanumeric word and there is at least one\ + \alphanumeric char after cursor" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + in + (* assert *) + Expect.isTrue (getChr app = #"o") + end) + , test + "moves cursor to last punctuation char in contiguous string\ + \when in punctuation word and there is at least one\ + \punctuation char after cursor" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "#$%!^ world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + in + (* assert *) + Expect.isTrue (getChr app = #"^") + end) + , test + "moves cursor to last char of next word,\ + \when cursor is on last char of current word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 4) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + in + (* assert *) + Expect.isTrue (getChr app = #"d") + end) + , test "does not break on undescore when cursor is on alphanumeric char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello_world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"d") + end) + , test "breaks on undescore when cursor is on punctuation char" (fn _ => + let + (* arrange *) + val app = TestUtils.init "#!^*(_#!@*(\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"(") + end) + , test "breaks on punctuation when cursor is on alphanumeric char" (fn _ => + (* vi's definition of 'word' instead of 'WORD' *) + let + (* arrange *) + val app = TestUtils.init "hello, world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"o") + end) + , test "breaks on alphanumeric char when cursor is on punctuation" (fn _ => + (* vi's definition of 'word' instead of 'WORD' *) + let + (* arrange *) + val app = TestUtils.init "!#%^()hello\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #")") + end) + , test "skips past tab" (fn _ => + let + (* arrange *) + val app = TestUtils.init "0123 \t \t \t 789\n" + val app = AppWith.idx (app, 4) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"9") + end) + , test + "moves cursor to last char in punctuation string \ + \when cursor is on punctuation" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "!!! hello\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = 2) + end) + , test "moves cursor to last char when cursor is on last word" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val chrIsEnd = getChr app = #"d" + in + Expect.isTrue chrIsEnd + end) + , test + "moves cursor to end of current word \ + \when current word is last word in line" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\n\nworld\n" + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val isAtEndOfHello = getChr app = #"o" + in + Expect.isTrue isAtEndOfHello + end) + ] + + val EMove = describe "move motion 'E'" + [ test "moves cursor to last char in WORD when in contiguous string" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hel!!!lo world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"E") + in + (* assert *) + Expect.isTrue (getChr app = #"o") + end) + , test + "moves cursor to last char of next WORD,\ + \when cursor is on last char of current WORD" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "#ELL) !@*(ORL$\n" + val app = AppWith.idx (app, 4) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"E") + in + (* assert *) + Expect.isTrue (getChr app = #"$") + end) + , test "does not break on punctuation when in alphanumeric char" (fn _ => + (* vi's definition of 'word' instead of 'WORD' *) + let + (* arrange *) + val app = TestUtils.init "hello, world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"E") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #",") + end) + , test "does not break on alphanumeric char when in punctuation" (fn _ => + (* vi's definition of 'word' instead of 'WORD' *) + let + (* arrange *) + val app = TestUtils.init "!#%^()hello world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"E") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"o") + end) + , test "skips past tab: '\\t'" (fn _ => + let + (* arrange *) + val app = TestUtils.init "0123 \t \t \t 789\n" + val app = AppWith.idx (app, 4) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"E") + + (* assert *) + val cursorChr = getChr app + in + Expect.isTrue (cursorChr = #"9") + end) + , test "moves cursor to last char when cursor is on last word" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world!\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"E") + in + (* assert *) + Expect.isTrue (getChr app = #"!") + end) + ] + + val bMove = describe "move motion 'b'" + [ test "leaves cursor at 0 when cursor is already at 0" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test "moves cursor previous word when on first character of next word" + (fn _ => + let + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 6) + val chr1 = getChr app + + val app2 = TestUtils.update (app, CHAR_EVENT #"b") + val chr2 = getChr app2 + in + Expect.isTrue (chr1 = #"w" andalso chr2 = #"h") + end) + , test "moves cursor to 0 when cursor > 0 and cursor is on first word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 3) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test + "moves cursor to first alphanumeric char after whitespace \ + \when in alphanumeric word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init " hello world\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (getChr app = #"h") + end) + , test + "moves cursor to first alphanumeric char after punctuation \ + \when in alphanumeric word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "!*#hello world\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (getChr app = #"h") + end) + , test + "moves cursor to first punctuation char after whitespace \ + \when in punctuation word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init " !@#$%^&*()\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (getChr app = #"!") + end) + , test + "moves cursor to first punctuation char after \ + \alphanumeric char when in punctuation word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "abc!@#$%^&*()\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (getChr app = #"!") + end) + , test "stops when char preceding word is newline" (fn _ => + let + (* arrange *) + val originalString = "hello\n\n\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, String.size originalString - 2) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (getChr app = #"w") + end) + ] + + val BMove = describe "move motion 'B'" + [ test "leaves cursor at 0 when cursor is already at 0" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"B") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test "moves cursor to 0 when cursor > 0 and cursor is on first WORD" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 3) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"B") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test + "moves cursor to first non-space char after whitespace \ + \when in WORD" + (fn _ => + let + (* arrange *) + val app = TestUtils.init " hello world\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"B") + in + (* assert *) + Expect.isTrue (getChr app = #"h") + end) + , test + "moves cursor to 0 when cursor is on first letter of first WORD \ + \and there are leadinng spaces before first letter" + (fn _ => + let + (* arrange *) + val app = TestUtils.init " hello world\n" + val app = AppWith.idx (app, 3) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"B") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test + "moves cursor to first char in WORD \ + \when in alphanumeric word preceded by punctuation" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "!*#hello world\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"B") + in + (* assert *) + Expect.isTrue (getChr app = #"!") + end) + , test "moves cursor to first char after whitespace when in WORD" (fn _ => + let + (* arrange *) + val app = TestUtils.init " !qwerty@#$%^&*()\n" + val app = AppWith.idx (app, 17) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"B") + in + (* assert *) + Expect.isTrue (getChr app = #"!") + end) + , test + "moves cursor to first char in WORD \ + \when in punctuation word preceded by alphanumeric" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "abc!@#$%^&*()\n" + val app = AppWith.idx (app, 11) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"B") + in + (* assert *) + Expect.isTrue (getChr app = #"a") + end) + ] + + + val zeroMove = describe "move motion '0'" + [ test "moves cursor to 0 when on first line" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello w7rld\n" + val app = AppWith.idx (app, 7) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"0") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test "leaves cursor on 0 when cursor is already on 0" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"0") + in + (* assert *) + Expect.isTrue (cursorIdx = 0) + end) + , test "leaves cursor at same idx when cursor is on '\\n'" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n hello again\n" + + val app = AppWith.idx (app, 11) + val {cursorIdx = oldIdx, ...} = app + + (* act *) + val {cursorIdx = newIdx, ...} = + TestUtils.update (app, CHAR_EVENT #"0") + in + (* assert *) + Expect.isTrue (oldIdx = newIdx) + end) + , test + "moves cursor to first char after newline when cursor is after first line" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n#ello again\n" + val app = AppWith.idx (app, 21) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"0") + + (* assert *) + val chr = getChr app + in + (* assert *) + Expect.isTrue (chr = #"#") + end) + , test "leaves cursor at same idx when on last newline" (fn _ => + let + (* arrange *) + val str = + "hello\n\ + \\n\ + \\n\ + \world\n\ + \\n\ + \\n" + + val initialCursorIdx = String.size str - 1 + val app = TestUtils.init str + val app = AppWith.idx (app, initialCursorIdx) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"0") + + (* assert *) + val expectedIdx = initialCursorIdx + in + (* assert *) + Expect.isTrue (cursorIdx = expectedIdx) + end) + ] + + val dlrMove = describe "move motion '$'" + [ test "moves cursor to char before '\\n' in contiguous string" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello wor9\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"$") + in + (* assert *) + Expect.isTrue (getChr app = #"9") + end) + , test + "leaves cursor at same idx in contiguous string\ + \when char after cursor is '\\n'" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n world\n" + val app = AppWith.idx (app, 11) + val oldIdx = #cursorIdx app + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"$") + val newIdx = #cursorIdx app + + val nchr = getChr app + val nchr = Char.toString nchr ^ "\n" + in + (* assert *) + Expect.isTrue (oldIdx = newIdx) + end) + , test "does not move cursor when cursor is on a newline" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\nworld\n" + val app = AppWith.idx (app, 6) + val oldIdx = #cursorIdx app + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"$") + val newIdx = #cursorIdx app + + val nchr = getChr app + val nchr = Char.toString nchr ^ "\n" + in + (* assert *) + Expect.isTrue (oldIdx = newIdx) + end) + ] + + val hatMove = describe "move motion '^'" + [ test + "moves cursor to first non-space char in first line\ + \when first line starts with spaces\ + \and cursor is on first space" + (fn _ => + let + (* arrange *) + val app = TestUtils.init " 3ello world\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"^") + in + (* assert *) + Expect.isTrue (getChr app = #"3") + end) + , test + "moves cursor to first non-space char in first line\ + \when first line starts with space\ + \and cursor is after first non-space char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init " 3ell7 world\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"^") + in + (* assert *) + Expect.isTrue (getChr app = #"3") + end) + , test + "moves cursor to first non-space char\ + \when cursor is after first line\ + \and before first non-space char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n world\n" + val app = AppWith.idx (app, 7) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"^") + in + (* assert *) + Expect.isTrue (getChr app = #"w") + end) + , test + "moves cursor to first non-space char\ + \when cursor is after first line\ + \and after first non-space char" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n world\n" + val app = AppWith.idx (app, 11) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"^") + in + (* assert *) + Expect.isTrue (getChr app = #"w") + end) + , test "leaves cursor in same position when on '\\n'" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hel\nlo\n" + val app = AppWith.idx (app, 3) + val oldIdx = #cursorIdx app + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"^") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = oldIdx) + end) + ] + + val GMove = describe "move motion 'G'" + [ test + "moves cursor to second last char in buffer, \ + \if last char is a newline preced by a non-newline" + (fn _ => + (* Note: We assume unix-style line endings: + * End of file always has \n at the end. *) + let + (* arrange *) + val str = "01234\n56789\n" + val app = TestUtils.init str + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"G") + in + (* assert *) + Expect.isTrue (#cursorIdx app = String.size str - 2) + end) + , test + "moves cursor to last char in buffer, \ + \if last char is a newline and second-last char is also a newline" + (fn _ => + let + (* arrange *) + val str = "01234\n5678\n\n" + val app = TestUtils.init str + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"G") + in + (* assert *) + Expect.isTrue (#cursorIdx app = String.size str - 1) + end) + , test + "moves cursor to last char in buffer, \ + \if last char is not a newline and second-last char \ + \is also not a newline" + (fn _ => + let + (* arrange *) + val str = "01234\n5678\n\n" + val app = TestUtils.init str + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"G") + in + (* assert *) + Expect.isTrue (#cursorIdx app = String.size str - 1) + end) + ] + + val percentMove = describe "move motion '%'" + [ test "moves to next ) when cursor is on (" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(hello)\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #")") + end) + , test "moves to preceding ( when cursur is on )" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(hello)\n" + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #"(") + end) + (* testing that cursor goes to correct level of nesting *) + , test "moves to outermost ) when cursor is on outermost (" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = 10) + end) + , test "moves to outermost ( when cursor is on outermost )" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = 0) + end) + , test "moves to middle ) when cursor is on middle (" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 1) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = 9) + end) + , test "moves to middle ( when cursor is on middle )" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 9) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = 1) + end) + , test "moves to innermost ) when cursor is on innermost (" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 2) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = 8) + end) + , test "moves to innermost ( when cursor is on innermost )" (fn _ => + let + (* arrange *) + val app = TestUtils.init "(((hello)))\n" + val app = AppWith.idx (app, 8) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = 2) + end) + (* testing different pair combinations *) + , test "moves to next ] when cursor is on [" (fn _ => + let + (* arrange *) + val app = TestUtils.init "[hello]\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #"]") + end) + , test "moves to preceding [ when cursur is on ]" (fn _ => + let + (* arrange *) + val app = TestUtils.init "[hello]\n" + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #"[") + end) + , test "moves to next } when cursor is on {" (fn _ => + let + (* arrange *) + val app = TestUtils.init "{hello}\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #"}") + end) + , test "moves to preceding { when cursur is on }" (fn _ => + let + (* arrange *) + val app = TestUtils.init "{hello}\n" + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #"{") + end) + , test "moves to next > when cursor is on <" (fn _ => + let + (* arrange *) + val app = TestUtils.init "\n" + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #">") + end) + , test "moves to preceding < when cursur is on >" (fn _ => + let + (* arrange *) + val app = TestUtils.init "\n" + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (getChr app = #"<") + end) + , test + "does not move when cursor is on a non-pair-character, \ + \and there is no pair-character where the cursor is at or after the cursor" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "he()o, world\n" + val app = AppWith.idx (app, 5) + val oldIdx = #cursorIdx app + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"%") + val newIdx = #cursorIdx app + in + (* assert *) + Expect.isTrue (newIdx = oldIdx) + end) + , test + "moves cursor when the cursor is not on a \ + \pair-character, but there is a pair-character \ + \after the cursor" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "he()o world\n" + val app = AppWith.idx (app, 0) + + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"%") + in + (* assert *) + Expect.isTrue (cursorIdx = 3) + end) + ] + + (* movements which use multiple chars *) + fun updateMany (app, str) = + let + fun loop (pos, app) = + if pos = String.size str then + app + else + let + val chr = String.sub (str, pos) + val app = TestUtils.update (app, CHAR_EVENT chr) + in + loop (pos + 1, app) + end + in + loop (0, app) + end + + val tMove = describe "move motion 't'" + [ test + "motion 'td' moves cursor to char before 'd' in string \"hello world\"" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app = updateMany (app, "td") + in + (* assert *) + Expect.isTrue (getChr app = #"l") + end) + , test "repeating 't' motion with same char does not move cursor" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app1 = updateMany (app, "td") + val app2 = updateMany (app1, "td") + in + (* assert *) + Expect.isTrue + (#cursorIdx app1 = #cursorIdx app2 andalso getChr app1 = #"l") + end) + , test + "does not move cursor at all when char following 't' is not in string" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app1 = updateMany (app, "t;") + in + (* assert *) + Expect.isTrue (#cursorIdx app1 = #cursorIdx app) + end) + , test "is cancellable by pressing escape" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"t") + val app2 = TestUtils.update (app1, KEY_ESC) + (* should not move cursor like other 't' tests do *) + val app3 = TestUtils.update (app2, CHAR_EVENT #"d") + in + (* assert *) + Expect.isTrue + (#cursorIdx app1 = #cursorIdx app2 + andalso #cursorIdx app2 = #cursorIdx app3) + end) + ] + + val TMove = describe "move motion 'T'" + [ test + "motion 'Th' moves cursor to char after 'h' in string \"hello world\"" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app = updateMany (app, "Th") + in + (* assert *) + Expect.isTrue (getChr app = #"e") + end) + , test "repeating 'T' motion with same char does not move cursor" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app1 = updateMany (app, "Te") + val app2 = updateMany (app1, "Te") + in + (* assert *) + Expect.isTrue + (#cursorIdx app1 = #cursorIdx app2 andalso getChr app1 = #"l") + end) + , test + "does not move cursor at all when char following 'T' is not in string" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app1 = updateMany (app, "T;") + in + (* assert *) + Expect.isTrue (#cursorIdx app1 = #cursorIdx app) + end) + , test "is cancellable by pressing escape" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"T") + val app2 = TestUtils.update (app1, KEY_ESC) + (* should ordinarily move cursor to 'w' but in this case should not + * as escape key should cancel motion which was in progress *) + val app3 = TestUtils.update (app2, CHAR_EVENT #" ") + in + (* assert *) + Expect.isTrue + (#cursorIdx app1 = #cursorIdx app2 + andalso #cursorIdx app2 = #cursorIdx app3) + end) + ] + + val fMove = describe "move motion 'f'" + [ test "motion 'fw' moves cursor to first 'w' in string \"hello world\"" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app = updateMany (app, "fw") + in + (* assert *) + Expect.isTrue (getChr app = #"w") + end) + , test "count followed by f moves forwards to count'th match" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app = updateMany (app, "3fl") + in + (* assert *) + Expect.isTrue (#cursorIdx app = 9 andalso getChr app = #"l") + end) + , test + "'count f' goes to last match when count is greater than number of chars" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app = updateMany (app, "9fl") + in + (* assert *) + Expect.isTrue (#cursorIdx app = 9 andalso getChr app = #"l") + end) + , test + "does not move cursor at all when char following 'f' is not in string" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app1 = updateMany (app, "f;") + in + (* assert *) + Expect.isTrue (#cursorIdx app1 = #cursorIdx app) + end) + , test "is cancellable by pressing escape" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"f") + val app2 = TestUtils.update (app1, KEY_ESC) + val app3 = TestUtils.update (app2, CHAR_EVENT #"d") + in + (* assert *) + Expect.isTrue + (#cursorIdx app1 = #cursorIdx app2 + andalso #cursorIdx app2 = #cursorIdx app3) + end) + ] + + val FMove = describe "move motion 'F'" + [ test "motion 'Fe' moves cursor to first 'e' before cursor" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app = updateMany (app, "Fe") + in + (* assert *) + Expect.isTrue (getChr app = #"e") + end) + , test "count followed by F moves backwards to count'th match" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app = updateMany (app, "3Fl") + in + (* assert *) + Expect.isTrue (#cursorIdx app = 2 andalso getChr app = #"l") + end) + , test + "'count F' goes to first match when count is greater than number of chars" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app = updateMany (app, "9Fl") + in + (* assert *) + Expect.isTrue (#cursorIdx app = 2 andalso getChr app = #"l") + end) + , test + "does not move cursor at all when char following 'F' is not in string" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app1 = updateMany (app, "F;") + in + (* assert *) + Expect.isTrue (#cursorIdx app1 = #cursorIdx app) + end) + , test "is cancellable by pressing escape" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"F") + val app2 = TestUtils.update (app1, KEY_ESC) + val app3 = TestUtils.update (app2, CHAR_EVENT #"r") + in + (* assert *) + Expect.isTrue + (#cursorIdx app1 = #cursorIdx app2 + andalso #cursorIdx app2 = #cursorIdx app3) + end) + ] + + val ggMove = describe "move motion 'gg'" + [ test "moves cursor to start when cursor is at end" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 10) + + (* act *) + val app = updateMany (app, "gg") + in + (* assert *) + Expect.isTrue (getChr app = #"h") + end) + , test "moves cursor to start when cursor is in middle" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 5) + + (* act *) + val app = updateMany (app, "gg") + in + (* assert *) + Expect.isTrue (getChr app = #"h") + end) + , test "leaves cursor in same place when cursor is already at start" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + + (* act *) + val app = updateMany (app, "gg") + in + (* assert *) + Expect.isTrue (getChr app = #"h") + end) + , test "is cancellable by pressing escape" (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world\n" + val app = AppWith.idx (app, 5) + + (* act *) + val app1 = TestUtils.update (app, CHAR_EVENT #"g") + val app2 = TestUtils.update (app1, KEY_ESC) + val app3 = TestUtils.update (app2, CHAR_EVENT #"g") + in + (* assert *) + Expect.isTrue (#cursorIdx app3 = 5) + end) + ] + + val tests = + [ hMove + , jMove + , kMove + , lMove + , wMove + , WMove + , bMove + , BMove + , eMove + , EMove + , zeroMove + , dlrMove + , hatMove + , GMove + , percentMove + (* multi-char motions *) + , tMove + , TMove + , fMove + , FMove + , ggMove + ] +end diff --git a/shf/test/normal-yank-tests.sml b/shf/test/normal-yank-tests.sml new file mode 100644 index 0000000..e3d6826 --- /dev/null +++ b/shf/test/normal-yank-tests.sml @@ -0,0 +1,836 @@ +structure NormalYankTests = +struct + open Railroad + open Railroad.Test + open InputMsg + + val yhYank = describe "yank motion 'yh'" + [ test "yanks empty string when cursor is at index 0" (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.updateMany (app, "yh") + + (* assert *) + val expectedString = "" + in + TestUtils.expectYank (app, expectedString) + end) + , test "yanks empty string when character before cursor is a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.updateMany (app, "yh") + + (* assert *) + val expectedString = "" + in + TestUtils.expectYank (app, expectedString) + end) + , test "yanks one char to the left when on a non-newline" (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 5) + + (* act *) + val app = TestUtils.updateMany (app, "yh") + + (* assert *) + val expectedString = "o" + in + TestUtils.expectYank (app, expectedString) + end) + , test "yanks 3 chars when count is 3" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "3yh") + + (* assert *) + val expectedString = "llo" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks from cursor position to start column when \ + \count is greater than current column" + (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "9yh") + + (* assert *) + val expectedString = "hello" + in + TestUtils.expectYank (app, expectedString) + end) + ] + + val ylYank = describe "yank motion 'yl'" + [ test "yanks last char in line when next char is newline" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 4 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yl") + + (* assert *) + val expectedString = "o" + in + TestUtils.expectYank (app, expectedString) + end) + , test "yanks char that cursor is currently on when not on newline" (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yl") + + (* assert *) + val expectedString = "h" + in + TestUtils.expectYank (app, expectedString) + end) + , test "yanks newline character when cursor is on a newline" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello\n\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yl") + + (* assert *) + val expectedString = "\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "does not yank past newline when specifying a range \ + \greater than number of columns" + (fn _ => + let + (* arrange *) + val originalIdx = 2 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yl") + + (* assert *) + val expectedString = "llo" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks last line, excluding newline, \ + \when cursor is on first character of last line \ + \and last line ends with a newline" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yl") + + (* assert *) + val expectedString = "world" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks last line, excluding newline, \ + \when cursor is on first character of last line \ + \and last line does not end with a newline" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yl") + + (* assert *) + val expectedString = "world" + in + TestUtils.expectYank (app, expectedString) + end) + ] + + val ykYank = describe "yank motion 'yk'" + [ test "does not yank when cursor is on first line" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yk") + in + (* assert *) + TestUtils.expectNoYank app + end) + , test + "yanks first two lines \ + \when there are two lines and cursor is on second line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 6 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yk") + + (* assert *) + val expectedString = "hello\nworld\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks last two lines when there are three lines in the buffer \ + \and cursor is on third line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val originalIdx = 15 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yk") + + (* assert *) + val expectedString = "world\nagain\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks whole buffer when on last line \ + \and count is greater than number of lines" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val originalIdx = 15 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yk") + + (* assert *) + val expectedString = originalString + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks newline and preceding line when cursor is second line \ + \and second line contains only a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\n\nagain\n" + val originalIdx = 6 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yk") + + (* assert *) + val expectedString = "hello\n\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks just newline and line above when cursor is on third line \ + \and third line contains only a newline" + (fn _ => + let + (* arrange *) + val originalString = + "hello\n\ + \world\n\ + \\n\ + \trello\n\ + \brillo\n" + val originalIdx = 12 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yk") + + (* assert *) + val expectedString = "world\n\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks second and third lines when cursor is on \ + \last non-newline character of third line" + (fn _ => + let + (* arrange *) + val originalString = "hello\n\nagain\n" + val originalString = + "hello\n\ + \world\n\ + \trello\n\ + \brillo\n" + val originalIdx = 17 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yk") + + (* assert *) + val expectedString = "world\ntrello\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks last two lines when cursor is on last line \ + \and last line only has a newline" + (fn _ => + let + (* arrange *) + val originalString = + "hello\n\ + \world\n\ + \\n" + val originalIdx = String.size originalString - 1 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yk") + + (* assert *) + val expectedString = "world\n\n" + in + TestUtils.expectYank (app, expectedString) + end) + ] + + val yjYank = describe "yank motion 'yj'" + [ test "does not yank any text when cursor is on last line" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = String.size originalString - 3 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yj") + in + (* assert *) + TestUtils.expectNoYank app + end) + , test "does not yank when there is only one line" (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yj") + in + (* assert *) + TestUtils.expectNoYank app + end) + , test + "yanks first two lines when cursor is on first line \ + \and there are at least two lines" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yj") + + (* assert *) + val expectedString = originalString + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks first two lines when there are three lines \ + \and cursor is on first line" + (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello\nworld\nbye world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yj") + + (* assert *) + val expectedString = "hello\nworld\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks last two lines when there are three lines \ + \and cursor is on second line" + (fn _ => + let + (* arrange *) + val originalIdx = 6 + val originalString = "hello\nworld\nbye world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yj") + + (* assert *) + val expectedString = "world\nbye world\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks entire file when cursor is on first line \ + \and a count is given which is larger \ + \than the total number of lines in the file" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yj") + + (* assert *) + val expectedString = originalString + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks entire file when a count greater than the total number of lines \ + \is given, while the file does not end with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yj") + + (* assert *) + val expectedString = originalString + in + TestUtils.expectYank (app, expectedString) + end) + , test "yanks two lines when cursor is on a newline" (fn _ => + let + (* arrange *) + val originalString = "\nhello\nworld\ntrello\nbrillo\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yj") + + (* assert *) + val expectedString = "\nhello\n" + in + TestUtils.expectYank (app, expectedString) + end) + ] + + val yyYank = describe "yank motion 'yy'" + [ test + "yanks last line when there is more than one line \ + \and cursor is on last line" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = String.size originalString - 3 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yy") + + (* assert *) + val expectedString = "world\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test "yanks whole buffer when buffer consists of one line" (fn _ => + let + (* arrange *) + val originalString = "hello\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yy") + + (* assert *) + val expectedString = originalString + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks first line when cursor is on first line \ + \and there are only two lines" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yy") + + (* assert *) + val expectedString = "hello\n" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks whole file when cursor is on first line \ + \and a count is given which is greater than \ + \the number of total lines" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yy") + + (* assert *) + val expectedString = originalString + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks whole file when cursor is on first line, \ + \count given is greater than number of lines, \ + \and the file does not end with a newline" + (fn _ => + let + (* arrange *) + val originalString = "hello\nworld" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "33yy") + + (* assert *) + val expectedString = originalString + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks just newline when cursor is on a line \ + \that contains only a single newline" + (fn _ => + let + (* arrange *) + val originalString = "\nhello\nworld\n" + val originalIdx = 0 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yy") + + (* assert *) + val expectedString = "\n" + in + TestUtils.expectYank (app, expectedString) + end) + ] + + fun yankSeconWordAlpha (pos, expectedString) = + let + (* arrange *) + val originalString = "hello world again\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, pos) + + (* act *) + val app = TestUtils.updateMany (app, "yw") + in + (* assert *) + TestUtils.expectYank (app, expectedString) + end + + val ywYank = describe "yank motion 'yw'" + [ test "yanks last character when cursor is on last character of line" + (fn _ => + let + (* arrange *) + val originalString = "hello world\n" + val originalIdx = String.size originalString - 2 + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = "d" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks from second word up to (and excluding) third word \ + \when cursor is on first character of second word" + (fn _ => yankSeconWordAlpha (6, "world ")) + , test + "yanks from second character of second word \ + \up to (and excluding) third word \ + \when cursor is on second character of second word" + (fn _ => yankSeconWordAlpha (7, "orld ")) + , test + "yanks from third character of second word \ + \up to (and excluding) third word \ + \when cursor is on third character of second word" + (fn _ => yankSeconWordAlpha (8, "rld ")) + , test + "yanks from fourth character of second word \ + \up to (and excluding) third word \ + \when cursor is on fourth character of second word" + (fn _ => yankSeconWordAlpha (9, "ld ")) + , test + "yanks from fifth character of second word \ + \up to (and excluding) third word \ + \when cursor is on fifth character of second word" + (fn _ => yankSeconWordAlpha (10, "d ")) + , test "yanks space when cursor is on space preceding an alpha char" + (fn _ => yankSeconWordAlpha (11, " ")) + , test "does not yank newline when cursor is on last word of line" (fn _ => + let + (* arrange *) + val originalString = "hello\nworld\nagain\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = "hello" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks until first punctuation char when on an alpha char \ + \and there is no space between alpha and punctuation" + (fn _ => + let + (* arrange *) + val originalString = "hello!world!again\n" + val app = TestUtils.init originalString + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = "hello" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks until first alpha char when on punctuation \ + \and there is no space between punctuation and alpha" + (fn _ => + let + (* arrange *) + val originalString = "!#%&QWERTY#!\n" + val app = TestUtils.init originalString + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = "!#%&" + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks until first alpha char (exluding) \ + \when cursor is on space and next char is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 1) + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = " " + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks until first alpha char \ + \when cursor is on space, many spaces are ahead, \ + \and first char after spaces is alpha" + (fn _ => + let + (* arrange *) + val originalString = "h ello\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 3) + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = " " + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks until first punctuation char \ + \when cursor is on space and next non-space char is punctuation" + (fn _ => + let + (* arrange *) + val originalString = "! @#$%\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, 2) + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = " " + in + TestUtils.expectYank (app, expectedString) + end) + , test + "yanks last char when on last word \ + \and there is no newline after current word" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello world" + val app = AppWith.idx (app, 6) + + (* act *) + val app = TestUtils.updateMany (app, "yw") + + (* assert *) + val expectedString = "world" + in + TestUtils.expectYank (app, expectedString) + end) + ] + + val tests = [yhYank, ylYank, ykYank, yjYank, yyYank, ywYank] +end diff --git a/shf/test/persistent-vector-tests.sml b/shf/test/persistent-vector-tests.sml new file mode 100644 index 0000000..7de91a5 --- /dev/null +++ b/shf/test/persistent-vector-tests.sml @@ -0,0 +1,630 @@ +structure PersistentVectorTests = +struct + open Railroad + open Railroad.Test + + fun isNotInRange (lst, pv) = + let + fun loopNotInRange lst = + case lst of + hd :: tl => + if PersistentVector.isInRange (hd, pv) then + let + val msg = + "idx " ^ Int.toString hd + ^ " is in range when it shouldn't be\n" + val () = print msg + in + Expect.isTrue false + end + else + loopNotInRange tl + | [] => Expect.isTrue true + in + loopNotInRange lst + end + + fun isInRange (lst, pv) = + let + fun loopInRange lst = + case lst of + hd :: tl => + if PersistentVector.isInRange (hd, pv) then + loopInRange tl + else + let + val msg = + "idx " ^ Int.toString hd + ^ " is not in range when it should be\n" + val () = print msg + in + Expect.isTrue false + end + | [] => Expect.isTrue true + in + loopInRange lst + end + + fun printVec pv = + let + val outputList = PersistentVector.toList pv + val str = + List.map + (fn {start, finish} => + "{start = " ^ Int.toString start ^ ", finish = " + ^ Int.toString finish ^ "}") outputList + val str = String.concatWith "\n " str ^ "\n" + in + print str + end + + val appendTests = describe "PersistentVector.append" + [ test "contains appended values in range" (fn _ => + let + (* arrange *) + val f = PersistentVector.append + val pv = PersistentVector.empty + + (* act *) + val pv = f (1, 3, pv) + val pv = f (5, 7, pv) + val pv = f (9, 13, pv) + val pv = f (19, 27, pv) + val pv = f (33, 33, pv) + + (* assert *) + (* we split the list into several smaller lists + * and then concatenate at the end + * so that the formatter does not cause + * each list element to take its own line *) + val indicesInRange1 = [1, 2, 3, 5, 6, 7, 9] + val indicesInRange2 = [10, 11, 12, 13, 19, 20] + val indicesInRange3 = [21, 22, 23, 24, 25, 26, 27, 33] + + val indicesInRange = + indicesInRange1 @ indicesInRange2 @ indicesInRange3 + in + isInRange (indicesInRange, pv) + end) + , test "does not contain values in range that were not appended" (fn _ => + let + (* arrange *) + val f = PersistentVector.append + val pv = PersistentVector.empty + (* act *) + val pv = f (1, 3, pv) + val pv = f (5, 7, pv) + val pv = f (9, 13, pv) + val pv = f (19, 27, pv) + val pv = f (33, 33, pv) + + (* assert *) + val indicesNotInRange = + [0, 4, 8, 14, 15, 16, 17, 18, 28, 29, 30, 31, 32, 34, 35] + in + isNotInRange (indicesNotInRange, pv) + end) + ] + + val toListTests = describe "PersistentVector.toList" + [ test "returns input list when input list has 5 elements" (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 3} + , {start = 5, finish = 7} + , {start = 9, finish = 13} + , {start = 19, finish = 27} + , {start = 33, finish = 33} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val outputList = PersistentVector.toList pv + + (* assert *) + in + Expect.isTrue (inputList = outputList) + end) + , test "returns input list when input list has more than 32 elements" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + , {start = 8, finish = 8} + , {start = 9, finish = 9} + , {start = 10, finish = 10} + , {start = 11, finish = 11} + , {start = 12, finish = 12} + , {start = 13, finish = 13} + , {start = 14, finish = 14} + , {start = 15, finish = 15} + , {start = 16, finish = 16} + , {start = 17, finish = 17} + , {start = 18, finish = 18} + , {start = 19, finish = 19} + , {start = 20, finish = 20} + , {start = 21, finish = 21} + , {start = 22, finish = 22} + , {start = 23, finish = 23} + , {start = 24, finish = 24} + , {start = 25, finish = 25} + , {start = 26, finish = 26} + , {start = 27, finish = 27} + , {start = 28, finish = 28} + , {start = 29, finish = 29} + , {start = 30, finish = 30} + , {start = 31, finish = 31} + , {start = 32, finish = 32} + , {start = 33, finish = 33} + , {start = 34, finish = 34} + , {start = 35, finish = 35} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val outputList = PersistentVector.toList pv + + (* assert *) + in + Expect.isTrue (inputList = outputList) + end) + ] + + val splitLeftTests = describe "PersistentVector.splitLeft" + [ test + "returns same vector when split idx is greater than any idx in vector" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + , {start = 8, finish = 8} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.splitLeft (9, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + in + Expect.isTrue (inputList = outputList) + end) + , test "removes last element when split idx is = to last element" (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + , {start = 8, finish = 8} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.splitLeft (8, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test "removes all elements when split idx = first element" (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + , {start = 8, finish = 8} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.splitLeft (1, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = [] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "removes element whose start and finish is in range \ + \of the split idx, and removes all elements after it too" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 155} + , {start = 200, finish = 200} + , {start = 210, finish = 210} + , {start = 220, finish = 220} + , {start = 230, finish = 230} + , {start = 240, finish = 240} + , {start = 250, finish = 250} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.splitLeft (7, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + ] + + val deleteTests = describe "PersistentVector.delete" + [ test "returns empty vector when deletion range includes every element" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + , {start = 8, finish = 8} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (0, 11, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = [] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "returns the left side of the vector \ + \when 'length' is greater than any element in the vector" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + , {start = 8, finish = 8} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (5, 4, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "decrements subsequent elements correctly \ + \when deletion range is before first element to middle element" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 50, finish = 50} + , {start = 60, finish = 60} + , {start = 70, finish = 70} + , {start = 80, finish = 80} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (0, 3, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 47, finish = 47} + , {start = 57, finish = 57} + , {start = 67, finish = 67} + , {start = 77, finish = 77} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "decrements subsequent elements correctly \ + \when deletion range is between two elements, \ + \but deletes no elements" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 3} + , {start = 15, finish = 19} + , {start = 35, finish = 39} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (21, 3, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 3} + , {start = 15, finish = 19} + , {start = 32, finish = 36} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test "deletes element when deletion range is inside that element" (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 3} + , {start = 15, finish = 19} + , {start = 35, finish = 39} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (17, 1, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [{start = 1, finish = 3}, {start = 34, finish = 38}] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "returns preceding elements when \ + \deletion range starts in middle and deletes to end of vector" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 6, finish = 6} + , {start = 7, finish = 7} + , {start = 8, finish = 8} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (5, 9, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "deletes middle elements and decrements subsequent elements \ + \when deletion range starts after first element \ + \and ends before last element" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 60, finish = 60} + , {start = 70, finish = 70} + , {start = 80, finish = 80} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (3, 3, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 57, finish = 57} + , {start = 67, finish = 67} + , {start = 77, finish = 77} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "maintains balance with all leaves at same depth \ + \when deleting a large portion of nodes in the middle" + (fn _ => + let + (* arrange *) + val inputList = List.tabulate (228, fn i => + {start = i, finish = i}) + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.delete (19, 15, pv) + + (* assert *) + val isBalanced = PersistentVector.allLeavesAtSameDepth pv + in + Expect.isTrue isBalanced + end) + ] + + val extendExistingMatchTests = describe "PersistentVector.extendExistingMatch" + [ test + "leaves subsequent matches untouched \ + \if their 'finish' is greater than the extended finish" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 60, finish = 60} + , {start = 70, finish = 70} + , {start = 80, finish = 80} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.extendExistingMatch (5, 50, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 50} + , {start = 60, finish = 60} + , {start = 70, finish = 70} + , {start = 80, finish = 80} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "removes subsequent matches whose 'finish' is less than \ + \the newly extended element's 'finish'" + (fn _ => + let + (* arrange *) + val inputList = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 5} + , {start = 60, finish = 60} + , {start = 70, finish = 70} + , {start = 80, finish = 80} + ] + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.extendExistingMatch (5, 75, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 75} + , {start = 80, finish = 80} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + , test + "removes all subsequent elements when new 'finish' is greater \ + \than any finish in the vector" + (fn _ => + let + (* arrange *) + val inputList = List.tabulate (500, fn i => + {start = i, finish = i}) + val pv = PersistentVector.fromList inputList + + (* act *) + val pv = PersistentVector.extendExistingMatch (5, 999, pv) + + (* assert *) + val outputList = PersistentVector.toList pv + val expectedOutput = + [ {start = 0, finish = 0} + , {start = 1, finish = 1} + , {start = 2, finish = 2} + , {start = 3, finish = 3} + , {start = 4, finish = 4} + , {start = 5, finish = 999} + ] + in + Expect.isTrue (outputList = expectedOutput) + end) + ] + + val tests = + [ appendTests + , toListTests + , splitLeftTests + , deleteTests + , extendExistingMatchTests + ] +end diff --git a/shf/test/regex-tests.sml b/shf/test/regex-tests.sml new file mode 100644 index 0000000..eddb47f --- /dev/null +++ b/shf/test/regex-tests.sml @@ -0,0 +1,618 @@ +structure RegexTests = +struct + open Railroad + open Railroad.Test + + structure CiDfa = CaseInsensitiveDfa + structure CsDfa = CaseSensitiveDfa + + val caseInsensitiveTests = describe "case insensitive regex" + [ test "recognises word 'hello' in string 'Hello world'" (fn _ => + let + (* arrange *) + val regexString = "hello" + val dfa = CiDfa.fromString regexString + val inputString = "Hello world" + + (* act *) + val matches = CiDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [(0, 4)] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "recognises word 'world' in string 'HELLO WORLD'" (fn _ => + let + (* arrange *) + val regexString = "world" + val dfa = CiDfa.fromString regexString + val inputString = "HELLO WORLD" + + (* act *) + val matches = CiDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [(6, 10)] + in + Expect.isTrue (matches = expectedMatches) + end) + ] + + val caseSensitiveTests = describe "case sensitive regex" + [ test "does not recognise word 'hello' in string 'Hello world'" (fn _ => + let + (* arrange *) + val regexString = "hello" + val dfa = CsDfa.fromString regexString + val inputString = "Hello world" + + (* act *) + val matches = CsDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "recognises word 'Hello' in string 'Hello world'" (fn _ => + let + (* arrange *) + val regexString = "Hello" + val dfa = CsDfa.fromString regexString + val inputString = "Hello world" + + (* act *) + val matches = CsDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [(0, 4)] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "does not recognise word 'world' in string 'HELLO WORLD'" (fn _ => + let + (* arrange *) + val regexString = "world" + val dfa = CsDfa.fromString regexString + val inputString = "HELLO WORLD" + + (* act *) + val matches = CsDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "recognises word 'WORLD' in string 'HELLO WORLD'" (fn _ => + let + (* arrange *) + val regexString = "WORLD" + val dfa = CsDfa.fromString regexString + val inputString = "HELLO WORLD" + + (* act *) + val matches = CsDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [(6, 10)] + in + Expect.isTrue (matches = expectedMatches) + end) + ] + + val endMarkerTests = describe "regex endMarker" + [ test "returns an empty DFA when regexString contains endMarker" (fn _ => + let + (* arrange *) + (* the end marker is #"\^@" *) + val regexString = "hello \^@ world" + + (* act *) + val dfa = CsDfa.fromString regexString + + (* assert *) + val actualLength = Vector.length dfa + val expectedLength = 0 + in + Expect.isTrue (actualLength = expectedLength) + end) + , test "matches a string when regex has question mark at the end" (fn _ => + let + (* arrange *) + val sentence = "favo" + val regexString = "favou?" + val dfa = CsDfa.fromString regexString + + (* act *) + val matches = CsDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(0, 3)] + in + Expect.isTrue (matches = expectedMatches) + end) + ] + + fun recogniseEscapeSequence (regexString, inputString) = + let + (* arrange *) + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [(6, 6)] + in + Expect.isTrue (matches = expectedMatches) + end + + fun doesNotRecogniseUnescaped (regexString, inputString) = + let + (* arrange *) + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, inputString) + + (* assert *) + val expectedMatches = [] + in + Expect.isTrue (matches = expectedMatches) + end + + val escapeSequenceTests = describe "regex escape sequences" + [ test "recognises alert" (fn _ => + recogniseEscapeSequence ("\\a", "hello \a world")) + , test "recognises backspace" (fn _ => + recogniseEscapeSequence ("\\b", "hello \b world")) + , test "recognises tab" (fn _ => + recogniseEscapeSequence ("\\t", "hello \t world")) + , test "recognises newline" (fn _ => + recogniseEscapeSequence ("\\n", "hello \n world")) + , test "recognises vertical tab" (fn _ => + recogniseEscapeSequence ("\\v", "hello \v world")) + , test "recognises form feed" (fn _ => + recogniseEscapeSequence ("\\f", "hello \f world")) + , test "recognises carriage return" (fn _ => + recogniseEscapeSequence ("\\r", "hello \r world")) + , test "recognises backslash" (fn _ => + recogniseEscapeSequence ("\\\\", "hello \\ world")) + ] + + val metacharacterEscapeTest = describe "regex metacharacter escape sequences" + [ test "recognises (" (fn _ => + recogniseEscapeSequence ("\\(", "hello ( world")) + , test "recognises )" (fn _ => + recogniseEscapeSequence ("\\)", "hello ) world")) + , test "recognises [" (fn _ => + recogniseEscapeSequence ("\\[", "hello [ world")) + , test "recognises ]" (fn _ => + recogniseEscapeSequence ("\\]", "hello ] world")) + , test "recognises +" (fn _ => + recogniseEscapeSequence ("\\+", "hello + world")) + , test "recognises |" (fn _ => + recogniseEscapeSequence ("\\|", "hello | world")) + , test "recognises ?" (fn _ => + recogniseEscapeSequence ("\\?", "hello ? world")) + , test "recognises ." (fn _ => + recogniseEscapeSequence ("\\.", "hello . world")) + , test "recognises -" (fn _ => + recogniseEscapeSequence ("\\-", "hello - world")) + + (* checking that unescaped metacharacter is not recognised *) + , test "does not recognise (" (fn _ => + doesNotRecogniseUnescaped ("(", "hello ( world")) + , test "does not recognise )" (fn _ => + doesNotRecogniseUnescaped (")", "hello ) world")) + , test "does not recognise [" (fn _ => + doesNotRecogniseUnescaped ("[", "hello [ world")) + , test "does not recognise ]" (fn _ => + doesNotRecogniseUnescaped ("[", "hello ] world")) + , test "does not recognise +" (fn _ => + doesNotRecogniseUnescaped ("+", "hello + world")) + , test "does not recognise |" (fn _ => + doesNotRecogniseUnescaped ("|", "hello | world")) + , test "does not recognise ?" (fn _ => + doesNotRecogniseUnescaped ("?", "hello ? world")) + , test "does not recognise -" (fn _ => + doesNotRecogniseUnescaped ("-", "hello - world")) + ] + + (* tests based on regex tutorial by FreeCodeCamp *) + val freeCodeCampTests = describe "regex freeCodeCamp tests" + [ test "The dog chased the cat" (fn _ => + let + (* arrange *) + val sentence = "The dog chased the cat" + val regexString = "the" + val caseSensitiveDfa = CsDfa.fromString regexString + val caseInsensitiveDfa = CiDfa.fromString regexString + + (* act *) + val caseSensitiveMatches = + CsDfa.matchString (caseSensitiveDfa, sentence) + val caseInsensitiveMatches = + CiDfa.matchString (caseInsensitiveDfa, sentence) + + (* assert *) + val expectedCaseSensitive = [(15, 17)] + val expectedCaseInsensitive = [(0, 2), (15, 17)] + val expected = + caseSensitiveMatches = expectedCaseSensitive + andalso caseInsensitiveMatches = expectedCaseInsensitive + in + Expect.isTrue (expected) + end) + , test "Somewhere Waldo is hiding in this text." (fn _ => + let + (* arrange *) + val sentence = "Somewhere Waldo is hiding in this text." + val regexString = "Waldo" + val dfa = CsDfa.fromString regexString + + (* act *) + val matches = CsDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(10, 14)] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "James has a pet cat." (fn _ => + let + (* arrange *) + val sentence = "James has a pet cat." + val regexString = "dog|cat|bird|fish" + val dfa = CsDfa.fromString regexString + + (* act *) + val matches = CsDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(16, 18)] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "Ignore Case While Matching" (fn _ => + let + (* arrange *) + val sentence = "freeCodeCamp" + val regexString = "freecodecamp" + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(0, 11)] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "Extract the word 'coding' from this string" (fn _ => + let + (* arrange *) + val sentence = "Extract the word 'coding' from this string" + val regexString = "coding" + val dfa = CsDfa.fromString regexString + + (* act *) + val matches = CsDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(18, 23)] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "Repeat, Repeat, Repeat" (fn _ => + let + (* arrange *) + val sentence = "Repeat, Repeat, Repeat" + val regexString = "Repeat" + val dfa = CsDfa.fromString regexString + + (* act *) + val matches = CsDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(0, 5), (8, 13), (16, 21)] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "Twinkle, twinkle, little start" (fn _ => + let + (* arrange *) + val sentence = "Twinkle, twinkle, little start" + val regexString = "twinkle" + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(0, 6), (9, 15)] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "hu. regex" (fn _ => + let + (* arrange *) + val regexString = "hu." + val dfa = CsDfa.fromString regexString + + val humStr = "To mock a humming bird" + val hugStr = "Bear hug" + + (* act *) + val humMatches = CiDfa.matchString (dfa, humStr) + val hugMatches = CiDfa.matchString (dfa, hugStr) + + (* assert *) + val expectedHumMatches = [(10, 12)] + val expectedHugMatches = [(5, 7)] + val isExpected = + humMatches = expectedHumMatches + andalso hugMatches = expectedHugMatches + in + Expect.isTrue isExpected + end) + , test "Let's have fun with regular expressions!" (fn _ => + let + (* arrange *) + val sentence = "Let's have fun with regular expressions!" + val regexString = ".un" + val dfa = CsDfa.fromString regexString + + (* act *) + val matches = CsDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(11, 13)] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "Beware of bugs in the above code" (fn _ => + let + (* arrange *) + val sentence = + "Beware of bugs in the above code;\ + \I have only proved it correct, not tried it." + val regexString = "[aeiou]" + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = + [ (1, 1) + , (3, 3) + , (5, 5) + , (7, 7) + , (11, 11) + , (15, 15) + , (20, 20) + , (22, 22) + , (24, 24) + , (26, 26) + , (29, 29) + , (31, 31) + , (33, 33) + , (36, 36) + , (38, 38) + , (40, 40) + , (47, 47) + , (49, 49) + , (52, 52) + , (56, 56) + , (59, 59) + , (65, 65) + , (70, 70) + , (71, 71) + , (74, 74) + ] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "The quick brown fox jumps over the lazy dog." (fn _ => + let + (* arrange *) + val sentence = "The quick brown fox jumps over the lazy dog." + val regexString = "[a-zA-Z]" + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = + [ (0, 0) + , (1, 1) + , (2, 2) + , (4, 4) + , (5, 5) + , (6, 6) + , (7, 7) + , (8, 8) + , (10, 10) + , (11, 11) + , (12, 12) + , (13, 13) + , (14, 14) + , (16, 16) + , (17, 17) + , (18, 18) + , (20, 20) + , (21, 21) + , (22, 22) + , (23, 23) + , (24, 24) + , (26, 26) + , (27, 27) + , (28, 28) + , (29, 29) + , (31, 31) + , (32, 32) + , (33, 33) + , (35, 35) + , (36, 36) + , (37, 37) + , (38, 38) + , (40, 40) + , (41, 41) + , (42, 42) + ] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "Blueberry 3.141592653s are delicious." (fn _ => + let + (* arrange *) + val sentence = "Blueberry 3.141592653s are delicious." + val regexString = "[2-6h-s]" + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = + [ (1, 1) + , (6, 6) + , (7, 7) + , (10, 10) + , (13, 13) + , (15, 15) + , (17, 17) + , (18, 18) + , (19, 19) + , (20, 20) + , (21, 21) + , (24, 24) + , (29, 29) + , (30, 30) + , (32, 32) + , (33, 33) + , (35, 35) + ] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "3 blind mice." (fn _ => + let + (* arrange *) + val sentence = "3 blind mice." + val regexString = "[^0-9aeiou]" + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = + [ (1, 1) + , (2, 2) + , (3, 3) + , (5, 5) + , (6, 6) + , (7, 7) + , (8, 8) + , (10, 10) + , (12, 12) + ] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "Mississipi" (fn _ => + let + (* arrange *) + val sentence = "Mississipi" + val regexString = "s+" + val dfa = CiDfa.fromString regexString + + (* act *) + val matches = CiDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(2, 3), (5, 6)] + in + Expect.isTrue (expectedMatches = matches) + end) + , test "goooal" (fn _ => + let + (* arrange *) + val soccerSentence = "goooal" + val gPhrase = "gut feeling" + val oPhrase = "over the moon" + + val goRegex = "go*" + val dfa = CsDfa.fromString goRegex + + (* act *) + val soccerMatches = CsDfa.matchString (dfa, soccerSentence) + val gPhraseMatches = CsDfa.matchString (dfa, gPhrase) + val oPhraseMatches = CsDfa.matchString (dfa, oPhrase) + + (* assert *) + val expectedSoccerMatches = [(0, 3)] + val expectedGPhraseMatches = [(0, 0), (10, 10)] + val expectedOPhraseMatches = [] + + val isExpected = + soccerMatches = expectedSoccerMatches + andalso gPhraseMatches = expectedGPhraseMatches + andalso oPhraseMatches = expectedOPhraseMatches + in + Expect.isTrue isExpected + end) + , test "chewie quote" (fn _ => + let + (* arrange *) + val sentence = "Aaaaaaargh" + val regexString = "Aa*" + val dfa = CsDfa.fromString regexString + + (* act *) + val matches = CsDfa.matchString (dfa, sentence) + + (* assert *) + val expectedMatches = [(0, 6)] + in + Expect.isTrue (matches = expectedMatches) + end) + , test "favorite" (fn _ => + let + (* arrange *) + val sentenceWithoutU = "favorite" + val sentenceWithU = "favourite" + + val regexString = "favou?rite" + val dfa = CsDfa.fromString regexString + + (* act *) + val matchesWithoutU = CsDfa.matchString (dfa, sentenceWithoutU) + val matchesWithU = CsDfa.matchString (dfa, sentenceWithU) + + (* assert *) + val expectedMatchesWithoutU = [(0, 7)] + val expectedMatchesWithU = [(0, 8)] + + val isExpected = + matchesWithoutU = expectedMatchesWithoutU + andalso matchesWithU = expectedMatchesWithU + in + Expect.isTrue isExpected + end) + ] + + val tests = + [ caseInsensitiveTests + , caseSensitiveTests + , endMarkerTests + , escapeSequenceTests + , metacharacterEscapeTest + , freeCodeCampTests + ] +end diff --git a/shf/test/regression-tests.sml b/shf/test/regression-tests.sml new file mode 100644 index 0000000..823ad3c --- /dev/null +++ b/shf/test/regression-tests.sml @@ -0,0 +1,82 @@ +structure RegressionTests = +struct + open Railroad + open Railroad.Test + + fun updateLoop (pos, str, app) = + if pos = String.size str then + app + else + let + val chr = String.sub (str, pos) + val () = ExceptionLogger.addCommand (InputMsg.CHAR_EVENT chr) + val app = TestUtils.update (app, InputMsg.CHAR_EVENT chr) + in + updateLoop (pos + 1, str, app) + end + + fun appFromText text = TestUtils.init text + + fun loadFromFile (io, acc) = + case TextIO.inputLine io of + SOME line => loadFromFile (io, acc ^ line) + | NONE => acc + + val initialText = + let + val io = TextIO.openIn "temp.txt" + val str = loadFromFile (io, "") + val () = TextIO.closeIn io + in + str + end + + val charEventTests = describe "CHAR_EVENT regressions" + [ test "SearchList.goToNum vector bounds regression (1)" (fn _ => + let + val app = TestUtils.init initialText + val history = "G12dk" + val newApp = TestUtils.updateMany (app, history) + in + (* just expect that we do not fail or throw an exception *) + Expect.isTrue true + end) + , test "No error raised when moving cursor up/down after deleting" (fn _ => + let + val app = TestUtils.init initialText + val history = + "16G18ddjjjjjjjjjdkdkdkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj" + val newApp = TestUtils.updateMany (app, history) + in + Expect.isTrue true + end) + , test + "SearchList.buildRange does not cause exception \ + \when deleting (1)" + (fn _ => + let + val app = TestUtils.init "h ello world\n" + + (* search *) + val search = "/ello" + val app = TestUtils.updateMany (app, search) + val app = TestUtils.update (app, InputMsg.KEY_ENTER) + + (* move and then delete twice *) + val app = TestUtils.updateMany (app, "edede") + in + Expect.isTrue true + end) + , test + "DfaGen does not cause exception \ + \when parsing alternation that contains a char \ + \from the previous alternation (1)" + (fn _ => + (let val dfa = CaseSensitiveDfa.fromString "str|s" + in Expect.isTrue true + end) + handle _ => Expect.isTrue false) + ] + + val tests = [charEventTests] +end diff --git a/shf/test/test-utils.sml b/shf/test/test-utils.sml new file mode 100644 index 0000000..4650d51 --- /dev/null +++ b/shf/test/test-utils.sml @@ -0,0 +1,71 @@ +structure TestUtils = +struct + fun init bufferString = + let val buffer = LineGap.fromString bufferString + in AppType.init (buffer, 0, 0, Time.now ()) + end + + fun update (app, cmd) = + AppUpdate.update (app, cmd, Time.now ()) + + fun updateMany (app, str) = + let + fun loop (pos, app) = + if pos = String.size str then + app + else + let + val chr = String.sub (str, pos) + val chr = InputMsg.CHAR_EVENT chr + val app = update (app, chr) + in + loop (pos + 1, app) + end + in + loop (0, app) + end + + fun expectYank (app: AppType.app_type, expectedString) = + let + open MailboxType + open DrawMsg + open Railroad + open Railroad.Test + + fun loop (hd :: tl) = + (case hd of + DRAW (YANK actualString) => + if actualString = expectedString then + Expect.isTrue (actualString = expectedString) + else + let + val () = print + ("expectedString = [" ^ expectedString ^ "]\n") + val () = print ("actualString = [" ^ actualString ^ "]\n") + val () = print "\n" + in + Expect.isTrue (actualString = expectedString) + end + | _ => loop tl) + | loop ([]) = + let val () = print "no string yanked\n" + in Expect.isTrue false + end + in + loop (#msgs app) + end + + fun expectNoYank (app: AppType.app_type) = + let + open MailboxType + open DrawMsg + open Railroad + open Railroad.Test + + fun loop (DRAW (YANK _) :: _) = Expect.isTrue false + | loop (hd :: tl) = loop tl + | loop ([]) = Expect.isTrue true + in + loop (#msgs app) + end +end diff --git a/shf/test/test.sml b/shf/test/test.sml new file mode 100644 index 0000000..bf93e53 --- /dev/null +++ b/shf/test/test.sml @@ -0,0 +1,23 @@ +structure Test = +struct + open Railroad + open Railroad.Test + + fun main () = + let + val tests = List.concat + [ NormalMoveTests.tests + , NormalDeleteTests.tests + , NormalYankTests.tests + , PersistentVectorTests.tests + , RegressionTests.tests + , RegexTests.tests + ] + val tests = concat tests + in + runWithConfig [Configuration.PrintPassed false] tests + handle e => ExceptionLogger.log e + end +end + +val () = Test.main () diff --git a/shf/todo.md b/shf/todo.md new file mode 100644 index 0000000..69f8799 --- /dev/null +++ b/shf/todo.md @@ -0,0 +1,12 @@ +# To-do list +- Add normal-delete tests for each motion, checking that searchList is as expected + - Three cases for searchList: + 1. Deletion causes two words to join to form a new match + 2. Deletion causes an existing match to be extended + 3. Deletion introduces no match +- Add tests for indent, dedent and remove-line-break motions + - Add tests that searchList updates as expected too +- Add tests for other yank motoins + - Tests should be based on existing tests for delete-motions, and in the same order. +- Bind gamepad functions from GLFW and/or RGFW +- Add tests for NormalYankDelete functions, to make sure that they are yanking the expected string.