diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index c3a6625..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.build/.added_strings_w3.jai b/.build/.added_strings_w3.jai index f08fdbd..8bbad12 100644 --- a/.build/.added_strings_w3.jai +++ b/.build/.added_strings_w3.jai @@ -5,8 +5,8 @@ // NAME :: "mexplore"; VERSION :: "0.1"; - JAI_VERSION :: "beta 0.2.016, built on 19 July 2025"; - RELEASE_DATE :: "15 September 2025, 21:57:10"; + JAI_VERSION :: "beta 0.2.018, built on 11 October 2025"; + RELEASE_DATE :: "21 October 2025, 23:02:07"; GIT_BRANCH :: "main"; - GIT_REVISION :: "7be01af7bf0025806f36ad7c938655e432d8b81e"; + GIT_REVISION :: "406a8a3a7118b28edb568656315ea190c111c5ba"; DEBUG :: true; diff --git a/.build/mexplore-debug.lib b/.build/mexplore-debug.lib index cbb51d7..5afe834 100644 Binary files a/.build/mexplore-debug.lib and b/.build/mexplore-debug.lib differ diff --git a/.build/mexplore-debug_0_w3.obj b/.build/mexplore-debug_0_w3.obj index be79542..dd8af8c 100644 Binary files a/.build/mexplore-debug_0_w3.obj and b/.build/mexplore-debug_0_w3.obj differ diff --git a/.build/mexplore-debug_1_w3.obj b/.build/mexplore-debug_1_w3.obj index 351c312..4489624 100644 Binary files a/.build/mexplore-debug_1_w3.obj and b/.build/mexplore-debug_1_w3.obj differ diff --git a/.build/mexplore-debug_2_w3.obj b/.build/mexplore-debug_2_w3.obj index 66142a2..3ce2a16 100644 Binary files a/.build/mexplore-debug_2_w3.obj and b/.build/mexplore-debug_2_w3.obj differ diff --git a/.build/mexplore-debug_3_w3.obj b/.build/mexplore-debug_3_w3.obj index 067923d..d3a031d 100644 Binary files a/.build/mexplore-debug_3_w3.obj and b/.build/mexplore-debug_3_w3.obj differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..12165a5 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# UI + +## Overview + +This is a small Jai library to do some basic immediate mode UI. It has a variety of widgets +and writing new ones should be relatively easy, if you follow the existing ones. + +As opposed to a UI library you might find on the Internet, like Dear ImGui, this library is +sadly not platform independent. It is relatively coupled with OpenGL, so you in order to +integrate it to a project, you will need to also provide a GL context with GLFW, RGFW, SDL, +or the like. + +## Architecture + +The main concept behind this UI system is that as you call the ui_* functions like ui_begin/ui_end, etc. +You are implicitly building the tree that represents the UI hierarchy. That is the magic of an +immediate mode API. You take advantage of the fact that functions are called in a stack across time. +Which is implicitly a Tree. At the end of the frame you give this tree to the rendering function, which +recurses through it pre-order, drawing the panels, icons, text, etc., in the correct order. + +Using this architecutre as the base places some undesirable constraints on the further design of the +system though. For example, the most useful part of this library is the auto-layout algorithm. It's not +super sophisticated, but it works. And in order to do this auto layout you need two basic pieces of +information. Where are the ui elements and how big are they. Properties that the layout algorithm can +itself change! + +So, the easiest solution to this problem is to separate everything into distinct phases (or passes). +In the first pass you are declaring the tree structure of the UI as you go in a bottom up fashion, +recording the initial positions and sizes. Then after you're done you can traverse the tree from the +top down adjusting the layout. This is only possible because at this point in time you have all the +information including nodes' siblings, the size they are, they size they'd like to be, etc. + +## Public API + +Some functions have a begin/end pair, becuase you necesarily have to wait until things finish happening +inside of them to be able to calculate their initial size, etc. Other functions don't have this begin/end +pair, because they don't allow children, they're a leaf node, and they don't need further information to finish +their calculations. + +``` +ui_new_frame :: () +``` + +This has to be called every frame, before calling any other UI function. + +``` +ui_begin :: (s: string) +``` + +This begins a panel, which other widgets can go inside of. + +``` +ui_end :: () +``` + +Has to be called after a ui_begin to finish its UI Node. + +``` +ui_text_input :: (s: string, font_colour := Vector4.{1, 1, 1, 1}) +``` + +This starts a text editor! + +``` +ui_begin_container :: (s: string, gradient := false, colour_left := Vector4.{}, colour_right := Vector4.{}, max_x: s32 = -1, max_y: s32 = -1, flags: ContainerFlags = 0) +``` + +Similar to a normal ui_begin, but you can have this one inside the main panel, recursively. +Very useful to change the layout direction (Left To Right vs Top to Bottom) + +``` +ui_end_container :: () +``` + +Must be called after a ui_begin_container, to calculate sizing and finish the node in the UI Tree. + +``` +ui_icon :: (s: string, texture: *Texture, size: Vector2) +``` + +Renders an OpenGL texture. + +``` +ui_label :: (s: string = "", colour := Vector4.{1.0, 1.0, 1.0, 1.0}) +``` + +Renders text. + +``` +ui_spacing :: (s: string) +``` + +Forces spacing between elements. The auto layout algorithm tries to maximize the space between +the node before the spacing and after the spacing, taking into account the max size of the parent +container. + +``` +ui_button :: (s: string = "", icon_texture: *Texture = null, font_colour := Vector4.{1, 1, 1, 1}, draw_frame := true) -> bool +``` + +Renders a panel with optional text in it and an optional OpenGL texture. When clicked it returns true. +Very useful to run conditional code like: + +if ui_button(...) { + do_something(); +} + +``` +ui_font_icon :: (font: *Font, s: string, size: Vector2, colour: Vector4 = .{1, 1, 1, 1}) +``` + +Renders an icon stored in a font. you have to figure out what the name of the glyph is and pray +FreeType can find it. You can look up the names using a tool like [FontDrop!](https://fontdrop.info/). + +``` +ui_progress_bar :: (s: string = "", progress: float, size: Vector2, background := Vector4.{1, 1, 1, 1}, foreground := Vector4.{1, 1, 1, 1}) +``` + +Renders a progress bar. + +``` +ui_end_frame :: () +``` + +Must be called after finishing the UI Tree. You must not call any UI Tree building functions (any of the previous ones) +after this call and before a new ui_begin_frame. + +``` +ui_render :: () +``` + +Renders the UI with OpenGL. + +## Example + +Please look at main.jai for an example. diff --git a/bin/mexplore-debug.exe b/bin/mexplore-debug.exe index 3e6bbda..427342e 100644 Binary files a/bin/mexplore-debug.exe and b/bin/mexplore-debug.exe differ diff --git a/bin/mexplore-debug.pdb b/bin/mexplore-debug.pdb index 3579fab..e91ec20 100644 Binary files a/bin/mexplore-debug.pdb and b/bin/mexplore-debug.pdb differ diff --git a/bin/mexplore-debug.rdi b/bin/mexplore-debug.rdi index 3440b2e..6741c03 100644 Binary files a/bin/mexplore-debug.rdi and b/bin/mexplore-debug.rdi differ diff --git a/modules/kb_text_shape/.build/.added_strings_w2.jai b/modules/kb_text_shape/.build/.added_strings_w2.jai index 8b2f9ca..d94e9c1 100644 --- a/modules/kb_text_shape/.build/.added_strings_w2.jai +++ b/modules/kb_text_shape/.build/.added_strings_w2.jai @@ -1,51 +1,51 @@ -// Workspace: Target Program - -// -// #insert text. Generated from /opt/jai/modules/Bindings_Generator/module.jai:326. -// - ([2] string).[ - .["FLT_MIN", "FLOAT32_MIN"], - .["FLT_MAX", "FLOAT32_MAX"], - .["DBL_MIN", "FLOAT64_MIN"], - .["DBL_MAX", "FLOAT64_MAX"], - - .["SCHAR_MIN", "S8_MIN"], - .["SCHAR_MAX", "S8_MAX"], - .["UCHAR_MIN", "0"], - .["UCHAR_MAX", "U8_MAX"], - - .["SHRT_MIN", "S16_MIN"], - .["SHRT_MAX", "S16_MAX"], - .["USHRT_MIN", "0"], - .["USHRT_MAX", "U16_MAX"], - - .["INT_MIN", "S32_MIN"], - .["INT_MAX", "S32_MAX"], - .["UINT_MIN", "0"], - .["UINT_MAX", "U32_MAX"], - - .["LLONG_MIN", "S64_MIN"], - .["LLONG_MAX", "S64_MAX"], - .["ULLONG_MIN", "0"], - .["ULLONG_MAX", "U64_MAX"], - - .["INT8_MIN", "S8_MIN"], - .["INT8_MAX", "S8_MAX"], - .["UINT8_MAX", "U8_MAX"], - - .["INT16_MIN", "S16_MIN"], - .["INT16_MAX", "S16_MAX"], - .["UINT16_MAX", "U16_MAX"], - - .["INT32_MIN", "S32_MIN"], - .["INT32_MAX", "S32_MAX"], - .["UINT32_MAX", "U32_MAX"], - - .["INT64_MIN", "S64_MIN"], - .["INT64_MAX", "S64_MAX"], - .["UINT64_MAX", "U64_MAX"], - .["LONG_MIN", "S64_MIN"], - .["LONG_MAX", "S64_MAX"], - .["ULONG_MIN", "0"], - .["ULONG_MAX", "U64_MAX"], -]; +// Workspace: Target Program + +// +// #insert text. Generated from C:/jai/modules/Bindings_Generator/module.jai:337. +// + ([2] string).[ + .["FLT_MIN", "FLOAT32_MIN"], + .["FLT_MAX", "FLOAT32_MAX"], + .["DBL_MIN", "FLOAT64_MIN"], + .["DBL_MAX", "FLOAT64_MAX"], + + .["SCHAR_MIN", "S8_MIN"], + .["SCHAR_MAX", "S8_MAX"], + .["UCHAR_MIN", "0"], + .["UCHAR_MAX", "U8_MAX"], + + .["SHRT_MIN", "S16_MIN"], + .["SHRT_MAX", "S16_MAX"], + .["USHRT_MIN", "0"], + .["USHRT_MAX", "U16_MAX"], + + .["INT_MIN", "S32_MIN"], + .["INT_MAX", "S32_MAX"], + .["UINT_MIN", "0"], + .["UINT_MAX", "U32_MAX"], + + .["LLONG_MIN", "S64_MIN"], + .["LLONG_MAX", "S64_MAX"], + .["ULLONG_MIN", "0"], + .["ULLONG_MAX", "U64_MAX"], + + .["INT8_MIN", "S8_MIN"], + .["INT8_MAX", "S8_MAX"], + .["UINT8_MAX", "U8_MAX"], + + .["INT16_MIN", "S16_MIN"], + .["INT16_MAX", "S16_MAX"], + .["UINT16_MAX", "U16_MAX"], + + .["INT32_MIN", "S32_MIN"], + .["INT32_MAX", "S32_MAX"], + .["UINT32_MAX", "U32_MAX"], + + .["INT64_MIN", "S64_MIN"], + .["INT64_MAX", "S64_MAX"], + .["UINT64_MAX", "U64_MAX"], + .["LONG_MIN", "S32_MIN"], + .["LONG_MAX", "S32_MAX"], + .["ULONG_MIN", "0"], + .["ULONG_MAX", "U32_MAX"], +]; diff --git a/modules/kb_text_shape/generate.jai b/modules/kb_text_shape/generate.jai index 4598b32..cf3e62d 100644 --- a/modules/kb_text_shape/generate.jai +++ b/modules/kb_text_shape/generate.jai @@ -108,8 +108,8 @@ generate_bindings :: (args: [] string, minimum_os_version: type_of(Build_Options try_to_preserve_comments = false; - array_add(*libpaths, lib_directory); - array_add(*libnames, LIB_BASE_NAME); + array_add(*library_search_paths, lib_directory); + array_add(*libraries, .{ filename = LIB_BASE_NAME}); array_add(*source_files, tprint("%.h", LIB_BASE_NAME)); generate_library_declarations = false; diff --git a/modules/kb_text_shape/kb_text_shape.h b/modules/kb_text_shape/kb_text_shape.h index 2ac5597..a949681 100644 --- a/modules/kb_text_shape/kb_text_shape.h +++ b/modules/kb_text_shape/kb_text_shape.h @@ -42,12 +42,45 @@ API Segmentation kbts_BeginBreak() - kbts_BreakAddCodepoint() -- Feed a codepoint to the breaker, call kbts_Break() immediately after this to get results + kbts_BreakAddCodepoint() -- Feed a codepoint to the breaker. + You need to call Break() repeatedly after every call to BreakAddCodepoint(). + Something like: + kbts_BreakAddCodepoint(&BreakState, ...); + kbts_break Break; + while(kbts_Break(&BreakState, &Break)) {...} + + When you call Break(), We guarantee that breaks are returned in-order. On our side, this means + that they are buffered and reordered. On your side, it means that there is a delay of a few + characters between your current position and the Break.Position that you will see. + + In some cases, our buffering might break. When that happens, we set + BREAK_STATE_FLAG_RAN_OUT_OF_REORDER_BUFFER_SPACE, and kbts_BreakStateIsValid() will return false. + This is a sticky error, so you can check it whenever you like. + To clear the error flag and start segmenting again, you will need to call BeginBreak(&BreakState), + which resets the entire state. + + Note that the input configurations for which our buffering breaks should be, for all intents and + purposes, nonsensical. If you find legitimate text that we cannot segment without running out of + buffer space, then that is a bug. + + The default buffer size is determined by the BREAK_REORDER_BUFFER_FLUSH_THRESHOLD. If you really + need a bigger buffer, then you might want to consider modifying this constant and recompiling + the library, although this should be viewed as an emergency solution and not a routine + configuration option. kbts_BreakFlush() kbts_Break() -- Call repeatedly to get breaks kbts_BreakStateIsValid() Easy font loading - kbts_FontFromFile() -- Open a font, byteswap it in place, and allocate auxiliary structures + kbts_FontFromFile() -- Open a font, byteswap it in place, and allocate auxiliary structures. + When you read a font with kb_text_shape, the library will byteswap its data in-place and perform + a bunch of other pre-computation passes to figure out memory limits and other useful information. + This means you cannot trivially pass our pointer to the font data to any other TTF library, since + they will expect the data to be in big endian format, which it won't be after we are done with it. + + You can expect font reading to be pretty slow. + On the other hand, you can expect shaping to be pretty fast. + + To open a font with your own IO and memory allocation, see "Manual Memory Management" below. kbts_FreeFont() kbts_FontIsValid() Shaping @@ -56,7 +89,33 @@ kbts_ShapeConfig() -- Bake a font/script-specific shaping configuration kbts_CodepointToGlyph() kbts_InferScript() -- Hacky script recognition for when no segmentation data is available - kbts_Shape() -- Returns 1 if more memory is needed, you should probably call this in a while() + kbts_Shape() -- Returns 1 if more memory is needed, you should probably call this in a while(). + This is how you might call this in practice: + while(kbts_Shape(State, &Config, Direction, Direction, Glyphs, &GlyphCount, GlyphCapacity)) + { + Glyphs = realloc(Glyphs, sizeof(kbts_glyph) * State->RequiredGlyphCapacity); + GlyphCapacity = State->RequiredGlyphCapacity; + } + Once Shape() returns 0, you are done shaping. Glyph indices are in the kbts_glyph.Id field. + Please note that, while the glyphs do also contain a Codepoint field, this field will mostly + be meaningless whenever complex shaping operations occur. This is because fonts exclusively + work on glyph indices, and a lot of ligatures are obviously a combination of several codepoints + and do not have a corresponding codepoint in the Unicode world. + The same is true when a single glyph is split into multiple glyphs. A font might decide to + decompose a letter-with-accent into a letter glyph + an accent glyph. In that case, we will + know what the accent glyph's index is, but we are not told what its codepoint is. + + There is currently no way to track where in the source text each glyph originates from. + One thing we might try is to have a "void *UserData" member on each glyph, and flow it through + the different substitutions, but I personally have not needed this yet and I do not have good + test cases for it. If you are interested, let me know! + + Final positions are in font units and can be extracted with Cursor() and PositionGlyph(). + To convert font units to fractional pixels in FreeType: + (FontX * FtSizeMetrics.x_scale) >> 16 + (FontY * FtSizeMetrics.y_scale) >> 16 + This will give you 26.6 fractional pixel units. + See https://freetype.org/freetype2/docs/reference/ft2-sizing_and_scaling.html for more info. kbts_ResetShapeState() Shaping - feature control kbts_FeatureOverride() -- Describe a manual override for a font feature @@ -74,9 +133,24 @@ Manual memory management kbts_SizeOfShapeState() kbts_PlaceShapeState() - kbts_ReadFontHeader() -- Read the top of the file and return how many bytes are needed to read the rest - kbts_ReadFontData() -- Read and byteswap the rest + kbts_ReadFontHeader() -- Read and byteswap the top of the file. + kbts_ReadFontData() -- Read and byteswap the rest. kbts_PostReadFontInitialize() -- Initialize auxiliary structures + Example code for reading a font file with this API looks like this: + size_t ScratchSize = kbts_ReadFontHeader(&Font, Data, Size); + size_t PermanentMemorySize = kbts_ReadFontData(&Font, malloc(ScratchSize), ScratchSize); + kbts_PostReadFontInitialize(&Font, malloc(PermanentMemorySize), PermanentMemorySize); + + Please note that, AS SOON AS YOU CALL ReadFontHeader(), THE FONT DATA IS MODIFIED IN-PLACE. + AS SOON AS YOU CALL ReadFontHeader(), THE FONT DATA IS MODIFIED IN-PLACE. + AS SOON AS YOU CALL ReadFontHeader(), THE FONT DATA IS MODIFIED IN-PLACE. + AS SOON AS YOU CALL ReadFontHeader(), THE FONT DATA IS MODIFIED IN-PLACE! + If you need to open the same font with another library, you need to copy the data BEFORE + calling ReadFontHeader(). + + The buffer you pass to ReadFontData() is temporary and can be freed once the function returns. + The buffer you pass to PostReadFontInitialize() is persistent and can only be freed once you + are done with the font. Utility, etc. kbts_ShaperIsComplex() kbts_ScriptIsComplex() @@ -98,7 +172,7 @@ kbts_direction Direction = KBTS_DIRECTION_NONE; for(size_t StringAt = 0; StringAt < Length;) { - kbts_decode Decode = kbts_DecodeUtf8(String, Length - StringAt); + kbts_decode Decode = kbts_DecodeUtf8(String + StringAt, Length - StringAt); StringAt += Decode.SourceCharactersConsumed; if(Decode.Valid) { @@ -174,6 +248,7 @@ Open a font with your own memory: kbts_font Font; + // Be careful: ReadFontHeader() and ReadFontData() both byteswap font data in-place! size_t ScratchSize = kbts_ReadFontHeader(&Font, Data, Size); size_t PermanentMemorySize = kbts_ReadFontData(&Font, malloc(ScratchSize), ScratchSize); kbts_PostReadFontInitialize(&Font, malloc(PermanentMemorySize), PermanentMemorySize); @@ -3038,7 +3113,7 @@ static kbts_script_properties kbts_ScriptProperties[KBTS_SCRIPT_COUNT] = { KBTS_EXPORT kbts_script kbts_ScriptTagToScript(kbts_script_tag Tag) { kbts_script Result = 0; - switch(Result) + switch(Tag) { case KBTS_SCRIPT_TAG_DONT_KNOW: Result = KBTS_SCRIPT_DONT_KNOW; break; case KBTS_SCRIPT_TAG_ADLAM: Result = KBTS_SCRIPT_ADLAM; break; @@ -15999,8 +16074,7 @@ KBTS_EXPORT kbts_glyph kbts_CodepointToGlyph(kbts_font *Font, kbts_u32 Codepoint { Result.Id = Cmap0->GlyphIdArray[Codepoint]; } - } - break; + } break; case 2: { @@ -16041,8 +16115,7 @@ KBTS_EXPORT kbts_glyph kbts_CodepointToGlyph(kbts_font *Font, kbts_u32 Codepoint Result.Id = GlyphId; } - } - break; + } break; case 4: { @@ -16052,31 +16125,36 @@ KBTS_EXPORT kbts_glyph kbts_CodepointToGlyph(kbts_font *Font, kbts_u32 Codepoint kbts_u16 *StartCodes = EndCodes + SegmentCount + 1; kbts_s16 *IdDeltas = (kbts_s16 *)(StartCodes + SegmentCount); kbts_u16 *IdRangeOffsets = (kbts_u16 *)(IdDeltas + SegmentCount); + kbts_un SegmentIndexOffset = 0; - KBTS_FOR(SegmentIndex, 0, SegmentCount) + if(SegmentCount) { - kbts_u16 Start = StartCodes[SegmentIndex]; - if((Codepoint >= Start) && (Codepoint <= EndCodes[SegmentIndex])) + while(SegmentCount > 1) { - kbts_s16 Delta = IdDeltas[SegmentIndex]; - kbts_u16 RangeOffset = IdRangeOffsets[SegmentIndex]; - - kbts_u16 GlyphId = (kbts_u16)Delta; - if(RangeOffset) - { - GlyphId += *(&IdRangeOffsets[SegmentIndex] + (Codepoint - Start) + RangeOffset / 2); - } - else - { - GlyphId += (kbts_u16)(Codepoint); - } - Result.Id = GlyphId; - - break; + kbts_un HalfCount = SegmentCount / 2; + SegmentIndexOffset = (EndCodes[SegmentIndexOffset + HalfCount - 1] < Codepoint) ? (SegmentIndexOffset + HalfCount) : SegmentIndexOffset; + SegmentCount -= HalfCount; } } - } - break; + + kbts_u16 Start = StartCodes[SegmentIndexOffset]; + if((Codepoint >= Start) && (Codepoint <= EndCodes[SegmentIndexOffset])) + { + kbts_s16 Delta = IdDeltas[SegmentIndexOffset]; + kbts_u16 RangeOffset = IdRangeOffsets[SegmentIndexOffset]; + + kbts_u16 GlyphId = (kbts_u16)Delta; + if(RangeOffset) + { + GlyphId += *(&IdRangeOffsets[SegmentIndexOffset] + (Codepoint - Start) + RangeOffset / 2); + } + else + { + GlyphId += (kbts_u16)(Codepoint); + } + Result.Id = GlyphId; + } + } break; case 6: { @@ -16088,8 +16166,7 @@ KBTS_EXPORT kbts_glyph kbts_CodepointToGlyph(kbts_font *Font, kbts_u32 Codepoint { Result.Id = GlyphIds[Offset]; } - } - break; + } break; case 12: { @@ -16097,22 +16174,25 @@ KBTS_EXPORT kbts_glyph kbts_CodepointToGlyph(kbts_font *Font, kbts_u32 Codepoint kbts_sequential_map_group *Groups = KBTS_POINTER_AFTER(kbts_sequential_map_group, Cmap12); kbts_un GlyphId = 0; - KBTS_FOR(GroupIndex, 0, Cmap12->GroupCount) + kbts_un GroupCount = Cmap12->GroupCount; + if(GroupCount) { - kbts_sequential_map_group *Group = &Groups[GroupIndex]; - - if((Codepoint >= Group->StartCharacterCode) && (Codepoint <= Group->EndCharacterCode)) + while(GroupCount > 1) { - kbts_un Offset = Codepoint - Group->StartCharacterCode; - GlyphId = Group->StartGlyphId + Offset; - - break; + kbts_un HalfCount = GroupCount / 2; + Groups = (Groups[HalfCount - 1].EndCharacterCode < Codepoint) ? (Groups + HalfCount) : Groups; + GroupCount -= HalfCount; } } + if((Codepoint >= Groups->StartCharacterCode) && (Codepoint <= Groups->EndCharacterCode)) + { + kbts_un Offset = Codepoint - Groups->StartCharacterCode; + GlyphId = Groups->StartGlyphId + Offset; + } + Result.Id = (kbts_u16)GlyphId; - } - break; + } break; } } @@ -23878,4 +23958,4 @@ KBTS_EXPORT int kbts_ScriptIsComplex(kbts_script Script) } #endif -#undef KBTS_X_FEATURES \ No newline at end of file +#undef KBTS_X_FEATURES diff --git a/modules/kb_text_shape/windows.jai b/modules/kb_text_shape/windows.jai index 58a2c69..a3cdafe 100644 --- a/modules/kb_text_shape/windows.jai +++ b/modules/kb_text_shape/windows.jai @@ -1,7 +1,7 @@ // // This file was auto-generated using the following command: // -// jai generate.jai - -compile -debug +// jai generate.jai // @@ -15,7 +15,8 @@ KBTS_MAX_SIMULTANEOUS_FEATURES :: 16; KBTS_BREAK_REORDER_BUFFER_FLUSH_THRESHOLD :: 4; KBTS_BREAK_REORDER_BUFFER_SIZE :: KBTS_BREAK_REORDER_BUFFER_FLUSH_THRESHOLD * 2; -kbts_joining_feature :: enum u8 { +kbts_joining_feature :: u8; +kbts_joining_feature_enum :: enum s32 { NONE :: 0; ISOL :: 1; @@ -41,7 +42,8 @@ kbts_joining_feature :: enum u8 { KBTS_JOINING_FEATURE_COUNT :: COUNT; } -kbts_reph_position :: enum u8 { +kbts_reph_position :: u8; +kbts_reph_position_enum :: enum s32 { AFTER_POST :: 0; BEFORE_POST :: 1; BEFORE_SUBJOINED :: 2; @@ -59,7 +61,8 @@ kbts_reph_position :: enum u8 { KBTS_REPH_POSITION_COUNT :: COUNT; } -kbts_reph_encoding :: enum u8 { +kbts_reph_encoding :: u8; +kbts_reph_encoding_enum :: enum s32 { IMPLICIT :: 0; EXPLICIT :: 1; LOGICAL_REPHA :: 2; @@ -75,7 +78,8 @@ kbts_reph_encoding :: enum u8 { KBTS_REPH_ENCODING_COUNT :: COUNT; } -kbts_syllabic_position :: enum u8 { +kbts_syllabic_position :: u8; +kbts_syllabic_position_enum :: enum s32 { NONE :: 0; RA_TO_BECOME_REPH :: 1; @@ -1533,7 +1537,8 @@ kbts_break_flags :: enum u32 { KBTS_BREAK_FLAG_ANY :: ANY; } -kbts_op_kind :: enum u8 { +kbts_op_kind :: u8; +kbts_op_kind_enum :: enum s32 { END :: 0; PRE_NORMALIZE_DOTTED_CIRCLES :: 1; @@ -1569,7 +1574,8 @@ kbts_op_kind :: enum u8 { KBTS_OP_KIND_COUNT :: COUNT; } -kbts_glyph_flags :: enum u32 { +kbts_glyph_flags :: u32; +kbts_glyph_flags_enum :: enum s32 { ISOL :: 1; FINA :: 2; FIN2 :: 4; @@ -1659,7 +1665,8 @@ kbts_japanese_line_break_style :: enum u8 { KBTS_JAPANESE_LINE_BREAK_STYLE_COUNT :: COUNT; } -kbts_orientation :: enum u32 { +kbts_orientation :: u32; +kbts_orientation_enum :: enum s32 { HORIZONTAL :: 0; VERTICAL :: 1; @@ -1685,7 +1692,8 @@ kbts_direction :: enum u32 { KBTS_DIRECTION_COUNT :: COUNT; } -kbts_unicode_joining_type :: enum u8 { +kbts_unicode_joining_type :: u8; +kbts_unicode_joining_type_enum :: enum s32 { NONE :: 0; LEFT :: 1; DUAL :: 2; @@ -1722,7 +1730,8 @@ kbts_unicode_flag_enum :: enum s32 { KBTS_UNICODE_FLAG_NON_SPACING_MARK :: NON_SPACING_MARK; } -kbts_unicode_bidirectional_class :: enum u8 { +kbts_unicode_bidirectional_class :: u8; +kbts_unicode_bidirectional_class_enum :: enum s32 { NI :: 0; L :: 1; R :: 2; @@ -1748,7 +1757,8 @@ kbts_unicode_bidirectional_class :: enum u8 { KBTS_UNICODE_BIDIRECTIONAL_CLASS_COUNT :: COUNT; } -kbts_line_break_class :: enum u8 { +kbts_line_break_class :: u8; +kbts_line_break_class_enum :: enum s32 { Onea :: 0; Oea :: 1; Ope :: 2; @@ -1896,7 +1906,8 @@ kbts_line_break_class :: enum u8 { KBTS_LINE_BREAK_CLASS_EOT :: EOT; } -kbts_word_break_class :: enum u8 { +kbts_word_break_class :: u8; +kbts_word_break_class_enum :: enum s32 { Onep :: 0; Oep :: 1; CR :: 2; @@ -1946,7 +1957,8 @@ kbts_word_break_class :: enum u8 { KBTS_WORD_BREAK_CLASS_SOT :: SOT; } -kbts_shaper :: enum u32 { +kbts_shaper :: u32; +kbts_shaper_enum :: enum s32 { DEFAULT :: 0; ARABIC :: 1; HANGUL :: 2; @@ -1972,7 +1984,8 @@ kbts_shaper :: enum u32 { KBTS_SHAPER_COUNT :: COUNT; } -kbts_script_tag :: enum u32 { +kbts_script_tag :: u32; +kbts_script_tag_enum :: enum s32 { DONT_KNOW :: 538976288; ADLAM :: 1835820129; AHOM :: 1836017761; @@ -2662,7 +2675,8 @@ kbts_script :: enum u32 { KBTS_SCRIPT_COUNT :: COUNT; } -kbts_feature_tag :: enum u32 { +kbts_feature_tag :: u32; +kbts_feature_tag_enum :: enum s32 { UNREGISTERED :: 0; isol :: 1819243369; fina :: 1634625894; @@ -2693,7 +2707,7 @@ kbts_feature_tag :: enum u32 { blwm :: 1836543074; blws :: 1937206370; calt :: 1953259875; - case_ :: 1702060387; + _case :: 1702060387; ccmp :: 1886217059; chws :: 1937205347; cjct :: 1952672355; @@ -2939,7 +2953,7 @@ kbts_feature_tag :: enum u32 { KBTS_FEATURE_TAG_blwm :: blwm; KBTS_FEATURE_TAG_blws :: blws; KBTS_FEATURE_TAG_calt :: calt; - KBTS_FEATURE_TAG_case :: case_; + KBTS_FEATURE_TAG_case :: _case; KBTS_FEATURE_TAG_ccmp :: ccmp; KBTS_FEATURE_TAG_chws :: chws; KBTS_FEATURE_TAG_cjct :: cjct; @@ -3156,7 +3170,8 @@ kbts_feature_tag :: enum u32 { KBTS_FEATURE_TAG_zero :: zero; } -kbts_feature_id :: enum u32 { +kbts_feature_id :: u32; +kbts_feature_id_enum :: enum s32 { UNREGISTERED :: 0; isol :: 1; fina :: 2; @@ -3187,7 +3202,7 @@ kbts_feature_id :: enum u32 { blwm :: 27; blws :: 28; calt :: 29; - case_ :: 30; + _case :: 30; ccmp :: 31; chws :: 32; cjct :: 33; @@ -3434,7 +3449,7 @@ kbts_feature_id :: enum u32 { KBTS_FEATURE_ID_blwm :: blwm; KBTS_FEATURE_ID_blws :: blws; KBTS_FEATURE_ID_calt :: calt; - KBTS_FEATURE_ID_case :: case_; + KBTS_FEATURE_ID_case :: _case; KBTS_FEATURE_ID_ccmp :: ccmp; KBTS_FEATURE_ID_chws :: chws; KBTS_FEATURE_ID_cjct :: cjct; @@ -3652,7 +3667,8 @@ kbts_feature_id :: enum u32 { KBTS_FEATURE_ID_COUNT :: COUNT; } -kbts_shaping_table :: enum u8 { +kbts_shaping_table :: u8; +kbts_shaping_table_enum :: enum s32 { GSUB :: 0; GPOS :: 1; COUNT :: 2; @@ -3932,7 +3948,8 @@ kbts_bracket :: struct { Script: u8; } -kbts_break_state_flags :: enum u32 { +kbts_break_state_flags :: u32; +kbts_break_state_flags_enum :: enum s32 { STARTED :: 1; END :: 2; RAN_OUT_OF_REORDER_BUFFER_SPACE :: 4; diff --git a/src/.build/.added_strings_w2.jai b/src/.build/.added_strings_w2.jai new file mode 100644 index 0000000..8d76809 --- /dev/null +++ b/src/.build/.added_strings_w2.jai @@ -0,0 +1,6 @@ +// Workspace: Target Program + +// +// String added via add_build_string() from C:/Users/vfs/Dev/Jails/bin/metaprogram/jails_diagnostics.jai:19. +// +JAILS_DIAGNOSTICS_BUILD :: true; diff --git a/src/main.jai b/src/main.jai index 1c8e474..b7c3daa 100644 --- a/src/main.jai +++ b/src/main.jai @@ -1,6 +1,6 @@ #import "Basic"; #import "GL"; -#import "freetype-2.12.1"; +#import "freetype"; #import "Hash_Table"; #import "Math"; #import "File"; @@ -26,9 +26,9 @@ using SDL3 :: #import "SDL3"; window_width :: 1280; window_height :: 720; -window : *SDL_Window; +window : *SDL_Window; gl_context : SDL_GLContext; -running : bool = true; +running : bool = true; dt : float; frame : u64; diff --git a/src/stb_textedit.jai b/src/stb_textedit.jai index d3ac63c..0541ed0 100644 --- a/src/stb_textedit.jai +++ b/src/stb_textedit.jai @@ -278,6 +278,8 @@ STB_TEXTEDIT_STRING :: TextInput; +STB_TEXTEDIT_GETWIDTH_NEWLINE :: -1.0; + STB_TEXTEDIT_STRINGLEN :: (obj: *STB_TEXTEDIT_STRING) -> s32 { return xx obj.text.count; } @@ -306,6 +308,7 @@ STB_TEXTEDIT_INSERTCHARS :: (obj: *STB_TEXTEDIT_STRING, i: s32, c: *STB_TEXTEDIT len := obj.text.count; + // if the string is empty add an initial new line. if n + len > obj.text.count { array_resize(*obj.text, n + len); } @@ -324,12 +327,23 @@ STB_TEXTEDIT_INSERTCHARS :: (obj: *STB_TEXTEDIT_STRING, i: s32, c: *STB_TEXTEDIT STB_TEXTEDIT_LAYOUTROW :: (row: *StbTexteditRow, obj: *STB_TEXTEDIT_STRING, n: s32) { idx : s32; - if n + idx < obj.text.count && row.x1 < obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding) { + + size, max_descent, read := next_line_size(obj.font, obj.text, n + idx, obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding)); + row.x1 = size.x; + row.baseline_y_delta = size.y; + row.ymin = max_descent; + row.ymax = size.y - row.ymin; + row.num_chars = read; + + return; + + + if n + idx < obj.text.count && row.x1 <= obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding) { size, max_descent, read := next_line_size(obj.font, obj.text, n + idx, obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding)); if row.x1 + size.x > obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding) { - while word := n + idx < obj.text.count && row.x1 < obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding) { + while word := n + idx < obj.text.count && row.x1 <= obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding) { size, max_descent, read := next_word_size(obj.font, obj.text, n + idx, obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding)); if row.x1 + size.x > obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding) { @@ -337,7 +351,6 @@ STB_TEXTEDIT_LAYOUTROW :: (row: *StbTexteditRow, obj: *STB_TEXTEDIT_STRING, n: s size, max_descent, read := next_grapheme_size(obj.font, obj.text, n + idx, obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding)); if row.x1 + size.x > obj.max_size.x - (2 * obj.margin + 2 * obj.border_size + 2 * obj.padding) { - //break word; return; } @@ -357,14 +370,13 @@ STB_TEXTEDIT_LAYOUTROW :: (row: *StbTexteditRow, obj: *STB_TEXTEDIT_STRING, n: s row.num_chars += read; idx += read; } + } else { + row.x1 = size.x; + row.baseline_y_delta = size.y; + row.ymin = max_descent; + row.ymax = size.y - row.ymin; + row.num_chars = read; } - - row.x1 += size.x; - row.baseline_y_delta = max(row.baseline_y_delta, size.y); - row.ymin = max(row.ymin, max_descent); - row.ymax = max(row.ymax, size.y - row.ymin); - row.num_chars += read; - idx += read; } row.x0 = 0.0; @@ -373,37 +385,54 @@ STB_TEXTEDIT_LAYOUTROW :: (row: *StbTexteditRow, obj: *STB_TEXTEDIT_STRING, n: s STB_TEXTEDIT_GETWIDTH :: (obj: *STB_TEXTEDIT_STRING, n: s32, i: s32) -> float { textp : *u32 = obj.text.data; Codepoints : *u32 = textp + n; - CodepointCount : u64 = xx (obj.text.count - (Codepoints - textp)); + CodepointCount : u64 = xx (obj.text.count - n); Cursor : kbts_cursor; Direction : kbts_direction = .NONE; Script : kbts_script = .DONT_KNOW; RunStart : u64 = 0; + BreakState : kbts_break_state; kbts_BeginBreak(*BreakState, .NONE, .NORMAL); width : float; - CodepointIndex : u64; - while CodepointIndex <= xx i { + CodepointIndex : s32 = 0; + while CodepointIndex < xx CodepointCount { kbts_BreakAddCodepoint(*BreakState, Codepoints[CodepointIndex], 1, xx ((CodepointIndex + 1) == xx CodepointCount)); Break : kbts_break; while kbts_Break(*BreakState, *Break) { - - if Break.Flags & .DIRECTION { - Direction = Break.Direction; - if(!Cursor.Direction) Cursor = kbts_Cursor(BreakState.MainDirection); - } - - if Break.Flags & .SCRIPT { - Script = Break.Script; - } + // if (Break.Position > RunStart) && (Break.Flags & (KBTS_BREAK_FLAG_DIRECTION | KBTS_BREAK_FLAG_SCRIPT | KBTS_BREAK_FLAG_LINE_HARD)) { + // RunLength : int = Break.Position - RunStart; + // ShapeText(*Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script); + // RunStart = Break.Position; + // } if (Break.Position > RunStart) && (Break.Flags & .GRAPHEME) { RunLength : u64 = Break.Position - RunStart; size, max_descent := ShapeText(obj.font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script); - width = size.x; + log("Grapheme: % Position: % Run Length: %, width: %", Codepoints[Break.Position - 1], Break.Position, RunLength, width); + if Break.Position == xx (i + 1) { + if Codepoints[Break.Position - 1] == STB_TEXTEDIT_NEWLINE { + width = STB_TEXTEDIT_GETWIDTH_NEWLINE; + return width; + } else { + return width; + } + } + + RunStart = Break.Position; + } + + if Break.Flags & .DIRECTION { + Direction = Break.Direction; + + if !Cursor.Direction Cursor = kbts_Cursor(BreakState.MainDirection); + } + + if Break.Flags & .SCRIPT { + Script = Break.Script; } } @@ -411,6 +440,95 @@ STB_TEXTEDIT_GETWIDTH :: (obj: *STB_TEXTEDIT_STRING, n: s32, i: s32) -> float { } return width; + + // Cursor : kbts_cursor; + // Direction : kbts_direction = .NONE; + // Script : kbts_script = .DONT_KNOW; + // RunStart : u64 = 0; + // BreakState : kbts_break_state; + // kbts_BeginBreak(*BreakState, .NONE, .NORMAL); + + // width : float; + + // CodepointIndex : u64; + // while CodepointIndex < CodepointCount { + // kbts_BreakAddCodepoint(*BreakState, Codepoints[CodepointIndex], 1, xx ((CodepointIndex + 1) == xx CodepointCount)); + // Break : kbts_break; + // while kbts_Break(*BreakState, *Break) { + + // if Break.Flags & .DIRECTION { + // Direction = Break.Direction; + // if(!Cursor.Direction) Cursor = kbts_Cursor(BreakState.MainDirection); + // } + + // if Break.Flags & .SCRIPT { + // Script = Break.Script; + // } + + // if Break.Flags & .GRAPHEME { + // RunLength : u64 = Break.Position - RunStart; + // if RunLength == 0 continue; + // size, max_descent := ShapeText(obj.font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script); + // width = size.x; + // if Break.Position == xx max(i, 1) { + // if Codepoints[Break.Position] == STB_TEXTEDIT_NEWLINE { + // width = STB_TEXTEDIT_GETWIDTH_NEWLINE; + // log("Grapheme: % Position: % Run Length: %, width: %", Codepoints[Break.Position], Break.Position, RunLength, width); + // return width; + // } else { + // log("Grapheme: % Position: % Run Length: %, width: %", Codepoints[Break.Position], Break.Position, RunLength, width); + // return width; + // } + // } + + + // RunStart = Break.Position; + // } + + // if Break.Flags & .WORD { + // RunLength : u64 = Break.Position - RunStart; + // log("Word: Position: % Run Length: %", Break.Position, RunLength); + // } + + // if Break.Flags & .LINE_SOFT { + // RunLength : u64 = Break.Position - RunStart; + // log("Line Soft: Position: % Run Length: %", Break.Position, RunLength); + // } + + // if Break.Flags & .LINE_HARD { + // RunLength : u64 = Break.Position - RunStart; + // log("Line Hard: Position: % Run Length: %", Break.Position, RunLength); + // } + + // // if (Break.Position >= RunStart) && (Break.Flags & .ANY) { + // // RunLength : u64 = Break.Position - RunStart; + // // if RunLength == 0 { + // // size, max_descent := ShapeText(obj.font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script); + + // // if Codepoints[Break.Position] == STB_TEXTEDIT_NEWLINE { + // // width = STB_TEXTEDIT_GETWIDTH_NEWLINE; + // // return width; + // // } else { + // // width = size.x; + // // } + // // } else if RunLength > 0 { + // // size, max_descent := ShapeText(obj.font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script); + + // // if Codepoints[Break.Position] == STB_TEXTEDIT_NEWLINE { + // // width = STB_TEXTEDIT_GETWIDTH_NEWLINE; + // // return width; + // // } else { + // // width = size.x; + // // return width; + // // } + // // } + // // } + // } + + // CodepointIndex += 1; + // } + + // return width; } STB_TEXTEDIT_K_LEFT :: 0x200000; // keyboard input to move cursor left @@ -558,6 +676,7 @@ stb_text_locate_coord :: (str: *STB_TEXTEDIT_STRING, x: float, y: float) -> s32 // search rows to find one that straddles 'y' while i < n { + r = .{}; STB_TEXTEDIT_LAYOUTROW(*r, str, i); if r.num_chars <= 0 return n; @@ -628,7 +747,7 @@ stb_textedit_drag :: (str: *STB_TEXTEDIT_STRING, state: *STB_TexteditState, x: f // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse // goes off the top or bottom of the text if state.single_line { - r :StbTexteditRow ; + r : StbTexteditRow ; STB_TEXTEDIT_LAYOUTROW(*r, str, 0); y = r.ymin; } @@ -669,10 +788,11 @@ stb_textedit_find_charpos :: (find: *StbFindState, str: *STB_TEXTEDIT_STRING, n: i : s32 = 0; first : s32; - if n == z { + if (str.text[str.text.count - 1] == STB_TEXTEDIT_NEWLINE && n == z - 1) || (n == z) { // if it's at the end, then find the last line -- simpler than trying to // explicitly handle this case in the regular code if single_line { + r = .{}; STB_TEXTEDIT_LAYOUTROW(*r, str, 0); find.y = 0; find.first_char = 0; @@ -684,13 +804,27 @@ stb_textedit_find_charpos :: (find: *StbFindState, str: *STB_TEXTEDIT_STRING, n: find.x = 0; find.height = 1; while (i < z) { + r = .{}; STB_TEXTEDIT_LAYOUTROW(*r, str, i); + if i + r.num_chars == z { + break; + } + prev_start = i; i += r.num_chars; } find.first_char = i; - find.length = 0; + find.length = r.num_chars; find.prev_first = prev_start; + + // now scan to find xpos + find.x = r.x0; + i = 0; + while first + i < n { + find.x += STB_TEXTEDIT_GETWIDTH(str, first, i); + + i += 1; + } } return; } @@ -699,6 +833,7 @@ stb_textedit_find_charpos :: (find: *StbFindState, str: *STB_TEXTEDIT_STRING, n: find.y = 0; while true { + r = .{}; STB_TEXTEDIT_LAYOUTROW(*r, str, i); if n < i + r.num_chars break; @@ -731,13 +866,25 @@ STB_TEXT_HAS_SELECTION :: (s: *STB_TexteditState) -> bool #expand { stb_textedit_clamp :: (str: *STB_TEXTEDIT_STRING, state: *STB_TexteditState) { n : s32 = STB_TEXTEDIT_STRINGLEN(str); if STB_TEXT_HAS_SELECTION(state) { - if (state.select_start > n) state.select_start = n; - if (state.select_end > n) state.select_end = n; + if str.text[str.text.count - 1] == STB_TEXTEDIT_NEWLINE && state.select_start > n - 1 { + state.select_start = n - 1; + } else if state.select_start > n + state.select_start = n; + + if str.text[str.text.count - 1] == STB_TEXTEDIT_NEWLINE && state.select_end > n - 1 { + state.select_end = n - 1; + } else if state.select_end > n + state.select_end = n; + // if clamping forced them to be equal, move the cursor to match if (state.select_start == state.select_end) state.cursor = state.select_start; } - if (state.cursor > n) state.cursor = n; + if str.text[str.text.count - 1] == STB_TEXTEDIT_NEWLINE && state.cursor > n - 1 { + state.cursor = n - 1; + } else if state.cursor > n { + state.cursor = n; + } } // delete characters while updating undo @@ -880,8 +1027,12 @@ while retry := true { ch : STB_TEXTEDIT_CHARTYPE = cast(STB_TEXTEDIT_CHARTYPE, c); // can't add newline in single-line mode - if c == #char "\n" && state.single_line + if c == #char "\n" && state.single_line { break; + } else if str.text.count == 0 { + new_line : u32 = #char "\n"; + STB_TEXTEDIT_INSERTCHARS(str, state.cursor, *new_line, 1); + } if state.insert_mode && !STB_TEXT_HAS_SELECTION(state) && state.cursor < STB_TEXTEDIT_STRINGLEN(str) { stb_text_makeundo_replace(str, state, state.cursor, 1, 1); @@ -1024,6 +1175,7 @@ while retry := true { // now find character position down a row state.cursor = start; + row = .{}; STB_TEXTEDIT_LAYOUTROW(*row, str, state.cursor); x = row.x0; for i : 0..row.num_chars - 1 { @@ -1090,6 +1242,7 @@ while retry := true { // now find character position up a row state.cursor = find.prev_first; + row = .{}; STB_TEXTEDIT_LAYOUTROW(*row, str, state.cursor); x = row.x0; for i : 0..row.num_chars - 1 { diff --git a/src/text.jai b/src/text.jai index 6a69b85..4b6e83e 100644 --- a/src/text.jai +++ b/src/text.jai @@ -36,8 +36,6 @@ Font :: struct { glyphs : Table(u32, Glyph); - //hb : *hb_font_t; - kb : kbts_font; texture : Texture; @@ -61,7 +59,6 @@ init_font :: (using font: *Font, filename: string, size: s32) { return; } - //FT_Set_Pixel_Sizes(face, 0, size); error = FT_Set_Char_Size(face, 0, size * 64, 0, 96); if error { log("%", to_string(FT_Error_String(error))); @@ -113,34 +110,6 @@ init_font :: (using font: *Font, filename: string, size: s32) { i += 1; } - // for 0..face.num_glyphs { - // //for 65..90 { - // //index := FT_Get_Char_Index(face, xx it); - // error = FT_Load_Glyph(face, cast(u32) it, FT_LOAD_RENDER); - // if error - // logd("%", to_string(FT_Error_String(error))); - - // array_add(*rects, stbrp_rect.{cast(s32) face.glyph.glyph_index, xx (face.glyph.bitmap.width + 2), - // xx (face.glyph.bitmap.rows + 2), 0, 0, 0}); - - // glyph : Glyph; - // glyph.utf32 = cast(u32) it; - // glyph.index = face.glyph.glyph_index; - // glyph.bearing_x = cast,trunc(s16) face.glyph.bitmap_left; - // glyph.bearing_y = cast,trunc(s16) face.glyph.bitmap_top; - // glyph.width = xx face.glyph.bitmap.width; - // glyph.height = xx face.glyph.bitmap.rows; - // glyph.rwidth = xx face.glyph.metrics.width >> 6; - // glyph.rheight = xx face.glyph.metrics.height >> 6; - // glyph.descent = xx (cast(float) face.glyph.bitmap.rows - cast(float) face.glyph.bitmap_top); - // glyph.ascent = xx (cast(float) face.glyph.bitmap.rows - glyph.descent); - // glyph.advance = cast(s16) face.glyph.advance.x >> 6; - // glyph.bitmap = alloc(glyph.height * glyph.width); - // memcpy(glyph.bitmap, face.glyph.bitmap.buffer, glyph.height * glyph.width); - - // table_set(*font.glyphs, glyph.index, glyph); - // } - stbrp_pack_rects(*stbrpcontext, rects.data, cast(s32) rects.count); @@ -181,33 +150,9 @@ init_font :: (using font: *Font, filename: string, size: s32) { } flip(atlas); - - // for rect : rects { - // glyph : *Glyph = table_find_pointer(*font.glyphs, cast(u32) rect.id); - // if (glyph.bitmap) { - // glyph.x = cast,trunc(s16) rect.x; - // glyph.y = ATLAS_SIZE - 1 - cast,trunc(s16) rect.y - xx glyph.height; - // glyph.st0 = .{cast(float, glyph.x) / ATLAS_SIZE, cast(float, glyph.y) / ATLAS_SIZE}; - // glyph.st1 = glyph.st0 + .{cast(float, glyph.width) / ATLAS_SIZE, cast(float, glyph.height) / ATLAS_SIZE}; - // } - // } texture = make_texture_from_data(atlas, ATLAS_SIZE, ATLAS_SIZE); - // blob : *hb_blob_t = hb_blob_create_or_fail(font_data.data, xx font_data.count, .HB_MEMORY_MODE_DUPLICATE, null, null); - // if blob == null { - // loge("Could not create the HarfBuzz blob."); - // } - - // hb_face : *hb_face_t = hb_face_create_or_fail(blob, 0); - // if hb_face == null { - // loge("Could not create the HarfBuzz face."); - // } - - // hb = hb_font_create(hb_face); - // hb_font_set_ppem(hb, xx pixel_size, xx pixel_size); - // hb_font_set_scale(hb, cast(s32) pixel_size * 64, cast(s32) pixel_size * 64); - ScratchSize : u64 = kbts_ReadFontHeader(*font.kb, font_data.data, xx font_data.count); PermanentMemorySize : u64 = kbts_ReadFontData(*font.kb, alloc(xx ScratchSize), ScratchSize); kbts_PostReadFontInitialize(*font.kb, alloc(xx PermanentMemorySize), PermanentMemorySize); @@ -271,15 +216,9 @@ render_text :: (font: *Font, text: []u32, pos: Vector2, size: Vector2, window_sp X, Y : s32; kbts_PositionGlyph(*Cursor, kglyph, *X, *Y); - // x_offset : hb_position_t = glyph_pos[i].x_offset; - // y_offset : hb_position_t = glyph_pos[i].y_offset; - // x_advance : hb_position_t = glyph_pos[i].x_advance; - // y_advance : hb_position_t = glyph_pos[i].y_advance; - glyph : *Glyph = table_find_pointer(*font.glyphs, kglyph.Id); if !glyph { - //log("[Error] Panic! Didn't find the glyph!"); GlyphIndex += 1; continue; } @@ -316,80 +255,11 @@ render_text :: (font: *Font, text: []u32, pos: Vector2, size: Vector2, window_sp array_add(*vertices, .{.{v0.x, v1.y}, .{t0.x, t1.y}, colour}); array_add(*vertices, .{v1, t1, colour}); - //render_pos += Vector2.{x_advance / 64.0, y_advance / 64.0}; - GlyphIndex += 1; } free(Glyphs); - //size, max_descent := ShapeText(font, *Cursor, text_utf32.data, xx text_utf32.count, Direction, Direction, Script); - - // buf : *hb_buffer_t = hb_buffer_create(); - // hb_buffer_add_utf8(buf, text.data, xx text.count, 0, -1); - - // hb_buffer_set_direction(buf, hb_direction_t.LTR); - // hb_buffer_set_script(buf, hb_script_t.HB_SCRIPT_LATIN); - // hb_buffer_set_language(buf, hb_language_from_string("en", -1)); - - // features : [1]hb_feature_t; - // features[0].tag = HB_TAG(#char "c", #char "a", #char "l", #char "t"); - // features[0].value = 1; - // features[0].start = HB_FEATURE_GLOBAL_START; - // features[0].end = HB_FEATURE_GLOBAL_END; - - // hb_shape(font.hb, buf, features.data, features.count); - - // glyph_count : u32; - // glyph_info : *hb_glyph_info_t = hb_buffer_get_glyph_infos(buf, *glyph_count); - // glyph_pos : *hb_glyph_position_t = hb_buffer_get_glyph_positions(buf, *glyph_count); - - - - // draw_size, max_ascent, max_descent := calculate_string_draw_size(font, text); - - // for i : 0..glyph_count - 1 { - // glyphid : hb_codepoint_t = glyph_info[i].codepoint; - // x_offset : hb_position_t = glyph_pos[i].x_offset; - // y_offset : hb_position_t = glyph_pos[i].y_offset; - // x_advance : hb_position_t = glyph_pos[i].x_advance; - // y_advance : hb_position_t = glyph_pos[i].y_advance; - - // glyph : *Glyph = table_find_pointer(*font.glyphs, glyphid); - - // v0 : Vector2; - // v1 : Vector2; - // if count_descent { - // v0 = render_pos + .{cast(float) x_offset + glyph.bearing_x, - // cast(float) y_offset - glyph.bearing_y + draw_size.y - max_descent}; - // } else { - // v0 = render_pos + .{cast(float) x_offset + glyph.bearing_x, - // cast(float) y_offset - (xx glyph.height - glyph.bearing_y)/* - glyph.height + draw_size.y*/}; - // } - - // v1 = v0 + Vector2.{cast(float) glyph.width, cast(float) glyph.height}; - - // // #if Y_IS_UP { - // // t0 := Vector2.{cast(float, glyph.x) / cast(float, ATLAS_SIZE), cast(float, glyph.y) / cast(float, ATLAS_SIZE)}; - // // t1 := t0 + Vector2.{cast(float, glyph.width) / cast(float, ATLAS_SIZE), -cast(float, glyph.height) / cast(float, ATLAS_SIZE)}; - // t0 := glyph.st0; - // t1 := glyph.st1; - // // } else { - // // t0 := Vector2.{cast(float, glyph.x / ATLAS_SIZE), cast(float, glyph.y / ATLAS_SIZE)}; - // // t1 := t0 + .{cast(float, glyph.width / ATLAS_SIZE), cast(float, glyph.height / ATLAS_SIZE)}; - // // } - - // array_add(*vertices, .{v0, t0, colour}); - // array_add(*vertices, .{.{v0.x, v1.y}, .{t0.x, t1.y}, colour}); - // array_add(*vertices, .{.{v1.x, v0.y}, .{t1.x, t0.y}, colour}); - - // array_add(*vertices, .{.{v1.x, v0.y}, .{t1.x, t0.y}, colour}); - // array_add(*vertices, .{.{v0.x, v1.y}, .{t0.x, t1.y}, colour}); - // array_add(*vertices, .{v1, t1, colour}); - - // render_pos += Vector2.{x_advance / 64.0, y_advance / 64.0}; - // } - // hb_buffer_destroy(buf); view := identity_of(Matrix4); proj := window_proj; @@ -400,58 +270,6 @@ render_text :: (font: *Font, text: []u32, pos: Vector2, size: Vector2, window_sp // restore_opengl_state(*opengl_state); } -// string_size :: (font: *Font, Cursor: *kbts_cursor, text: string, MainDirection: kbts_direction, Direction: kbts_direction, Script: kbts_script) -> Vector2 { -// text_utf32 : [..]u32; -// defer array_free(text_utf32); - -// StringAt : u64; -// while StringAt < xx text.count { -// Decode : kbts_decode = kbts_DecodeUtf8(text.data, xx text.count - StringAt); -// StringAt += Decode.SourceCharactersConsumed; -// if Decode.Valid { -// array_add(*text_utf32, Decode.Codepoint); -// } -// } - -// Glyphs : *kbts_glyph = cast(*kbts_glyph, alloc(size_of(kbts_glyph) * text_utf32.count)); - -// CodepointIndex: u64; -// while CodepointIndex < xx text_utf32.count { -// Glyphs[CodepointIndex] = kbts_CodepointToGlyph(*font.kb, text_utf32[CodepointIndex]); - -// CodepointIndex += 1; -// } - -// State : *kbts_shape_state = kbts_CreateShapeState(*font.kb); -// Config : kbts_shape_config = kbts_ShapeConfig(*font.kb, Script, xx kbts_language_enum.KBTS_LANGUAGE_DONT_KNOW); - -// size : Vector2; - -// GlyphCount : u32 = xx text_utf32.count; -// GlyphCapacity : u32 = GlyphCount; -// while kbts_Shape(State, *Config, MainDirection, Direction, Glyphs, *GlyphCount, GlyphCapacity) { -// Glyphs = cast(*kbts_glyph, realloc(Glyphs, size_of(kbts_glyph) * State.RequiredGlyphCapacity, size_of(kbts_glyph) * GlyphCapacity)); -// GlyphCapacity = State.RequiredGlyphCapacity; -// } - -// GlyphIndex : u64; -// while GlyphIndex < GlyphCount { -// Glyph : *kbts_glyph = *Glyphs[GlyphIndex]; - -// X : s32; -// Y : s32; -// kbts_PositionGlyph(Cursor, Glyph, *X, *Y); - -// //RenderGlyph(Glyph, X, Y); - -// GlyphIndex += 1; -// } - -// free(Glyphs); - -// return size; -// } - ShapeText :: (font: *Font, Cursor: *kbts_cursor, Codepoints: *u32, CodepointCount: u64, MainDirection: kbts_direction, Direction: kbts_direction, Script: kbts_script) -> Vector2, float { kglyphs : *kbts_glyph = cast(*kbts_glyph, alloc(xx (size_of(kbts_glyph) * CodepointCount))); @@ -488,12 +306,8 @@ ShapeText :: (font: *Font, Cursor: *kbts_cursor, Codepoints: *u32, CodepointCoun size.y = max(size.y, xx glyph.height); size.x += xx (FT_MulFix(kglyph.AdvanceX, font.face.size.metrics.x_scale) >> 6); max_descent = max(max_descent, xx glyph.descent); - //line.max_descent = max(line.max_descent, cast(float) glyph.descent); - //line.max_ascent = max(line.max_ascent, cast(float) glyph.ascent); } - //RenderGlyph(Glyph, X, Y); - GlyphIndex += 1; } @@ -555,9 +369,6 @@ next_word_size :: (font : *Font, text : []u32, n : s32, max_width : float = 0.0) BreakState : kbts_break_state; kbts_BeginBreak(*BreakState, .NONE, .NORMAL); - // size : Vector2; - // max_descent : float; - CodepointIndex : u64; while CodepointIndex < xx CodepointCount { kbts_BreakAddCodepoint(*BreakState, Codepoints[CodepointIndex], 1, xx ((CodepointIndex + 1) == xx CodepointCount)); diff --git a/src/ui.jai b/src/ui.jai index c340b8a..78ea915 100644 --- a/src/ui.jai +++ b/src/ui.jai @@ -78,7 +78,6 @@ UIContext :: struct { sizing_y : UISizing; offaxis_layout : UIOffAxisLayout; - // font : *Simp.Dynamic_Font; font : *Font; margin : s32 = 2; @@ -159,7 +158,6 @@ Label :: struct { text : string; text_size : Vector2; max_descent : float; - //lines : [..]Line; } Icon :: struct { @@ -313,7 +311,7 @@ ui_render :: () { ui_begin :: (s: string) { text, key := ui_decompose_and_generate_id(null, s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); root : *Rect = xx ptr; if !success { new_root := New(Rect); @@ -382,7 +380,7 @@ global_max_width : float = 100.0; ui_label :: (s: string = "", colour := Vector4.{1.0, 1.0, 1.0, 1.0}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); label : *Label = xx ptr; if !success { new_label := New(Label); @@ -534,7 +532,7 @@ ui_draw_label :: (using label: *Label) { ui_icon :: (s: string, texture: *Texture, size: Vector2) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); icon : *Icon = xx ptr; if !success { new_icon := New(Icon); @@ -580,7 +578,7 @@ ui_draw_icon :: (using icon: *Icon) { ui_font_icon :: (font: *Font, s: string, size: Vector2, colour: Vector4 = .{1, 1, 1, 1}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); icon : *FontIcon = xx ptr; if !success { new_icon := New(FontIcon); @@ -613,10 +611,6 @@ ui_font_icon :: (font: *Font, s: string, size: Vector2, colour: Vector4 = .{1, 1 ui_draw_font_icon :: (using font_icon: *FontIcon) { midline : Vector2 = pos + Vector2.{cast(float, margin + border_size + padding), xx (size.y / 2.0)}; - // label_size := Vector2.{xx Simp.prepare_icon(font, name), xx font.character_height}; - - // Simp.draw_prepared_text(font, xx midline.x, xx (midline.y - label_size.y / 2.0), colour); - // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); @@ -633,7 +627,7 @@ ui_draw_font_icon :: (using font_icon: *FontIcon) { ui_button :: (s: string = "", icon_texture: *Texture = null, font_colour := Vector4.{1, 1, 1, 1}, draw_frame := true) -> bool { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); button : *Button = xx ptr; if !success { new_button := New(Button); @@ -773,7 +767,7 @@ ui_draw_button :: (using button: *Button) { ui_scrollbar :: (s: string = "", icon_texture: *Texture = null, font_colour := Vector4.{1, 1, 1, 1}, draw_frame := true) -> bool { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); scrollbar : *ScrollBar = xx ptr; if !success { new_scrollbar := New(ScrollBar); @@ -884,7 +878,7 @@ ui_draw_scroll_bar :: (using scroll_bar: *ScrollBar) { ui_text_input :: (s: string, font_colour := Vector4.{1, 1, 1, 1}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); text_input : *TextInput = xx ptr; if !success { new_text_input := New(TextInput); @@ -906,32 +900,22 @@ ui_text_input :: (s: string, font_colour := Vector4.{1, 1, 1, 1}) { ui_append_to_parent(text_input); text_size : Vector2; - //text_size.y = xx text_input.font.face.size.metrics.height >> 6; - //str := builder_to_string(*text_input.input_buffer, 0, false); - // if str.count > 0 { - // defer free(str); - // textsize = Vector2.{xx Simp.prepare_text(text_input.font, str), xx text_input.font.character_height}; - // } max_descent : float; - // idx : s32; - // while idx < text_input.text.count { - // word_size, word_descent, read := next_word_size(text_input.font, text_input.text, idx, 0.0); - - // text_size.x += word_size.x; - // text_size.y = max(text_size.y, word_size.y); - // max_descent = max(max_descent, word_descent); - - // idx += read; - // } i : s32; while i < text_input.text.count { row : StbTexteditRow; STB_TEXTEDIT_LAYOUTROW(*row, text_input, i); + is_new_line : bool = text_input.text[i + row.num_chars - 1] == #char "\n"; + text_size.x = max(text_size.x, row.x1); text_size.y += text_input.font.face.size.metrics.height >> 6; + // if is_new_line && text_input.text.count <= i + row.num_chars { + // text_size.y += text_input.font.face.size.metrics.height >> 6; + // } + i += xx row.num_chars; } @@ -1005,23 +989,28 @@ ui_draw_text_input :: (using text_input: *TextInput) { row : StbTexteditRow; STB_TEXTEDIT_LAYOUTROW(*row, text_input, i); + is_new_line : bool = text_input.text[i + row.num_chars - 1] == #char "\n"; + select_rectangle : Rectangle; - if i + xx row.num_chars >= textedit_state.cursor { + log("Cursor: %", textedit_state.cursor); + + if i + row.num_chars >= textedit_state.cursor && !(is_new_line && textedit_state.cursor == i + row.num_chars) { + j : s32; while grapheme := i + j < textedit_state.cursor { - size, max_descent, read := next_grapheme_size(font, text, i + j, 0.0); + size, max_descent, read := next_grapheme_size(font, text, i + j, 0.0); - cursor_pos.x += size.x; - j += read; + cursor_pos.x += size.x; + + j += read; } } else { cursor_pos.y -= font.face.size.metrics.height >> 6; } render_text(font, .{row.num_chars, text_input.text.data + i}, - .{draw_pos.x, draw_pos.y + row.ymin}, text_input.size, - colour = font_colour); + .{draw_pos.x, draw_pos.y + row.ymin}, text_input.size,colour = font_colour); if textedit_state.select_start < textedit_state.select_end { @@ -1087,6 +1076,72 @@ ui_draw_text_input :: (using text_input: *TextInput) { } } + render_filled_rectangle(select_pos + v2(margin) + v2(border_size) + v2(padding), select_size, .{0.8, 0.2, 0.55, 0.4}); + } + } else if textedit_state.select_start > textedit_state.select_end { + + if textedit_state.select_start > i && textedit_state.select_end < i + row.num_chars { + + select_size : Vector2 = .{0.0, xx font.face.size.metrics.height >> 6}; + + if textedit_state.select_end < i && textedit_state.select_start > i + row.num_chars { + select_pos.x = 0.0; + select_size.x = row.x1 - row.x0; + } + + if textedit_state.select_end >= i && textedit_state.select_start > i + row.num_chars { + j : s32; + while grapheme := i + j < textedit_state.select_end && i + j < i + row.num_chars { + size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); + + if segment_type & .GRAPHEME { + select_pos.x += size.x; + j += read; + } else { + select_pos.y -= font.face.size.metrics.height >> 6; + } + } + + select_size.x = row.x1 - select_pos.x; + } + + if textedit_state.select_end < i && textedit_state.select_start <= i + row.num_chars { + select_pos.x = 0.0; + + j : s32; + while grapheme := i + j < textedit_state.select_start && i + j < i + row.num_chars { + size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); + + if segment_type & .GRAPHEME { + select_size.x += size.x; + j += read; + } + } + } + + if textedit_state.select_end >= i && textedit_state.select_start <= i + row.num_chars { + j : s32; + + while grapheme := i + j < textedit_state.select_end && i + j < i + row.num_chars { + size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); + + if segment_type & .GRAPHEME { + select_pos.x += size.x; + j += read; + } + } + + while grapheme := i + j < textedit_state.select_start && i + j < i + row.num_chars { + size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); + + if segment_type & .GRAPHEME { + select_size.x += size.x; + j += read; + + } + } + } + render_filled_rectangle(select_pos + v2(margin) + v2(border_size) + v2(padding), select_size, .{0.8, 0.2, 0.55, 0.4}); } } @@ -1117,7 +1172,7 @@ ui_draw_text_input :: (using text_input: *TextInput) { ui_begin_container :: (s: string, gradient := false, colour_left := Vector4.{}, colour_right := Vector4.{}, max_x: s32 = -1, max_y: s32 = -1, flags: ContainerFlags = 0) -> bool { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); container : *Container = xx ptr; if !success { new_container := New(Container); @@ -1219,7 +1274,7 @@ ui_draw_container :: (using container: *Container) { ui_spacing :: (s: string) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); spacing : *Spacing = xx ptr; if !success { new_spacing := New(Spacing); @@ -1251,7 +1306,7 @@ ui_spacing :: (s: string) { ui_progress_bar :: (s: string = "", progress: float, size: Vector2, background := Vector4.{1, 1, 1, 1}, foreground := Vector4.{1, 1, 1, 1}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); - success, ptr := table_find_new(*ui_context.rects, key); + success, ptr := table_find(*ui_context.rects, key); bar : *ProgressBar = xx ptr; if !success { new_bar := New(ProgressBar); diff --git a/ui.rad b/ui.rad deleted file mode 100644 index 0b6adfc..0000000 --- a/ui.rad +++ /dev/null @@ -1,45 +0,0 @@ -// raddbg 0.9.21 project file - -recent_file: path: "src/stb_textedit.jai" -recent_file: path: "src/ui.jai" -recent_file: path: "../../../../jai/modules/runtime_support.jai" -recent_file: path: "src/main.jai" -recent_file: path: "src/text.jai" -recent_file: path: "../../../../jai/modules/basic/array.jai" -recent_file: path: "../../../../jai/modules/default_allocator/module.jai" -recent_file: path: "../../../../jai/modules/math/module.jai" -recent_file: path: "modules/kb_text_shape/kb_text_shape.h" -recent_file: path: "modules/SDL3/src/SDL-release-3.2.16/src/events/sdl_keyboard.c" -recent_file: path: "modules/SDL3/src/SDL-release-3.2.16/src/video/windows/SDL_windowsevents.c" -recent_file: path: "src/math/math.jai" -recent_file: path: "../../../../jai/modules/basic/module.jai" -target: -{ - executable: "bin/mexplore-debug.exe" - working_directory: bin - enabled: 1 -} -breakpoint: -{ - source_location: "src/ui.jai:1032:1" - hit_count: 0 - enabled: 0 -} -breakpoint: -{ - source_location: "src/stb_textedit.jai:995:1" - hit_count: 0 - enabled: 0 -} -breakpoint: -{ - source_location: "src/stb_textedit.jai:1016:1" - hit_count: 0 - enabled: 0 -} -breakpoint: -{ - source_location: "src/stb_textedit.jai:1030:1" - enabled: 0 - hit_count: 0 -}