From 5ba82ea7e12b99fc8efc3ca26c421c1070c3964d Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Sun, 31 Aug 2025 01:15:29 +0100 Subject: [PATCH] if we can fit at least 80 chars in a single line on the screen, centre all visible text on the screen --- fcore/text-builder.sml | 77 ++++-- temp.txt | 543 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 600 insertions(+), 20 deletions(-) diff --git a/fcore/text-builder.sml b/fcore/text-builder.sml index 164d0d3..91abde3 100644 --- a/fcore/text-builder.sml +++ b/fcore/text-builder.sml @@ -19,6 +19,7 @@ end structure TextBuilder :> TEXT_BUILDER = struct open TextConstants + structure TC = TextConstants type env_data = { r: Real32.real @@ -30,6 +31,13 @@ struct , hg: Real32.real , hb: Real32.real + , startX: int + , startY: int + + (* w = width, h = height. + * These do no necessarily correspond to the whole window's width and height. + * For example, in some cases we want to horizontally centre text on the screen. + * In that case, "w" means "end width/pixel, which causes a line break". *) , w: int , h: int @@ -734,6 +742,48 @@ struct | [] => (* should never happen *) 0 end + fun initEnv (windowWidth, windowHeight, msgs) = + let + val fw = Real32.fromInt windowWidth + val fh = Real32.fromInt windowHeight + in + if TC.textLineWidth > windowWidth then + { w = windowWidth + , h = windowHeight + , startX = 5 + , startY = 5 + , fw = fw + , fh = fh + , r = 0.67 + , g = 0.51 + , b = 0.83 + , hr = 0.211 + , hg = 0.219 + , hb = 0.25 + , msgs = msgs + } + else + let + val startX = (windowWidth - TC.textLineWidth) div 2 + val finishWidth = startX + TC.textLineWidth + in + { w = finishWidth + , h = windowHeight + , startX = startX + , startY = 5 + , fw = fw + , fh = fh + , r = 0.67 + , g = 0.51 + , b = 0.83 + , hr = 0.211 + , hg = 0.219 + , hb = 0.25 + , msgs = msgs + } + end + end + fun build ( startLine , cursorPos @@ -756,19 +806,8 @@ struct (* get absolute idx of line *) val absIdx = curIdx + startIdx - val env = - { w = windowWidth - , h = windowHeight - , fw = Real32.fromInt windowWidth - , fh = Real32.fromInt windowHeight - , r = 0.67 - , g = 0.51 - , b = 0.83 - , hr = 0.211 - , hg = 0.219 - , hb = 0.25 - , msgs = msgs - } + val env = initEnv (windowWidth, windowHeight, msgs) + val {startX, startY, ...} = env val cursorAcc = Vector.fromList [] val searchPos = BinSearch.equalOrMore (absIdx, searchList) @@ -778,9 +817,9 @@ struct ( startIdx , rStrHd , [] - , 5 - , 5 - , 5 + , startX + , startY + , startX , rStrTl , absIdx , cursorPos @@ -796,9 +835,9 @@ struct ( startIdx , rStrHd , [] - , 5 - , 5 - , 5 + , startX + , startY + , startX , rStrTl , absIdx , cursorPos diff --git a/temp.txt b/temp.txt index db2fcbc..d6ea8a2 100644 --- a/temp.txt +++ b/temp.txt @@ -1 +1,542 @@ -abcabc +signature TEXT_BUILDER = +aaron baron carrot durian + (* Prerequisite: LineGap is moved to requested line first. *) + val build: int * int * LineGap.t * int * int + -> MailboxType.t list +end + + + +structure TextBuilder :> TEXT_BUILDER = +struct + val xSpace = 13 + val xSpace3 = xSpace * 3 + val ySpace = 25 + val fontSize = 30.0 + + fun accToDrawMsg (textAcc, cursorAcc) = + let + open MailboxType + open DrawMsg + + val textAcc = Vector.concat textAcc + val cursorAcc = Vector.concat cursorAcc + + val textMsg = REDRAW_TEXT textAcc + val cursorMsg = REDRAW_CURSOR cursorAcc + in + [DRAW textMsg, DRAW cursorMsg] + end + + fun buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g, b) = + let + val left = posX + 9 + val left = Real32.fromInt left + val right = left + 12.0 + + val top = Real32.fromInt posY + val bottom = top + fontSize + 2.0 + + val halfHeight = fWindowHeight / 2.0 + val top = (~(top - halfHeight)) / halfHeight + val bottom = (~(bottom - halfHeight)) / halfHeight + + val halfWidth = fWindowWidth / 2.0 + val left = (left - halfWidth) / halfWidth + val right = (right - halfWidth) / halfWidth + + val vec = + #[ left, top, r, g, b + , right, top, r, g, b + , left, bottom, r, g, b + + , left, bottom, r, g, b + , right, bottom, r, g, b + , right, top, r, g, b + ] + in + [vec] + end + + (* builds text from a string with char-wrap. + * char-wrap is a similar concept to word-wrap, + * but it breaks on character in the middle of a word. + * + * Will likely want multiple versions of these two mutually recursive + * functions for each selection and cursor type: + * cursor over an individual character, + * range selection where multiple characters are selected, etc. + * + * Todo: + * - Possibly add visual horizontal indentation when char-wrap occurs + * on an indented line *) + fun buildTextStringAfterCursor + ( pos, str, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) = + if pos < String.size str then + case String.sub (str, pos) of + #" " => + (* if space, then proceed forward one char + * without adding to acc *) + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + | #"\t" => + (* if tab, proceed forward one char, + * and jump visually forwards three chars *) + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace3, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + | #"\n" => + (* if \n, move down vertically, and move to start horizontally + * assuming we have not exceeded the window's height. + * If we have exceeded the window's height, just return acc. *) + if posY + ySpace < windowHeight then + buildTextStringAfterCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + else + (* return if there is no more vertical space after line break *) + accToDrawMsg (acc, cursorAcc) + | #"\r" => + (* same as \n, except we also check if we are in a \r\n pair, + * and proceed two characters forward if so *) + if posY + ySpace < windowHeight then + if + pos < String.size str - 1 + andalso String.sub (str, pos + 1) = #"\n" + then + buildTextStringAfterCursor + ( pos + 2, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + else + buildTextStringAfterCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + else + (* return if there is no more vertical space after line break *) + accToDrawMsg (acc, cursorAcc) + | chr => + (* for any other character, add it to acc if there is space, + * and proceed forward one character in the string *) + let + val chrFun = Vector.sub (CozetteAscii.asciiTable, Char.ord chr) + in + if posX + xSpace < windowWidth then + (* if there is horizontal space, place char on the right *) + let + val chrVec = chrFun + (posX, posY, fontSize, fontSize, fWindowWidth, fWindowHeight, r, g, b) + val acc = chrVec :: acc + in + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else if posY + ySpace < windowHeight then + (* if there is vertical space, place char down below at startX *) + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , r, g, b + ) + val acc = chrVec :: acc + in + buildTextStringAfterCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else + (* return if no space horizontally or vertically *) + accToDrawMsg (acc, cursorAcc) + end + else + (* if we reached the end of the string, + * call function to build next string *) + continueBuildTextLineGapAfterCursor + ( tl, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, cursorAcc + ) + + and continueBuildTextLineGapAfterCursor + ( strList, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, cursorAcc + ) = + case strList of + hd :: tl => + buildTextStringAfterCursor + ( 0, hd, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + | [] => accToDrawMsg (acc, cursorAcc) + + (* same as buildTextStringAfterCursor, except this keeps track of absolute + * index and cursor pos too *) + fun buildTextStringBeforeCursor + ( pos, str, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, hr, hg, hb + ) = + if pos < String.size str then + case String.sub (str, pos) of + #" " => + buildTextStringBeforeCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + | #"\t" => + buildTextStringBeforeCursor + ( pos + 1, str, acc, posX + xSpace3, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + | #"\n" => + if posY + ySpace < windowHeight then + buildTextStringBeforeCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + else + accToDrawMsg (acc, []) + | #"\r" => + if posY + ySpace < windowHeight then + if + pos < String.size str - 1 + andalso String.sub (str, pos + 1) = #"\n" + then + buildTextStringBeforeCursor + ( pos + 2, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 2, cursorPos, hr, hg, hb + ) + else + buildTextStringBeforeCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + else + accToDrawMsg (acc, []) + | chr => + let + val chrFun = Vector.sub (CozetteAscii.asciiTable, Char.ord chr) + in + if posX + xSpace < windowWidth then + let + val chrVec = chrFun + (posX, posY, fontSize, fontSize, fWindowWidth, fWindowHeight, r, g, b) + val acc = chrVec :: acc + in + buildTextStringBeforeCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + end + else if posY + ySpace < windowHeight then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , r, g, b + ) + val acc = chrVec :: acc + in + buildTextStringBeforeCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + end + else + accToDrawMsg (acc, []) + end + else + continueBuildTextLineGapBeforeCursor + ( tl, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, absIdx, cursorPos, hr, hg, hb + ) + + and buildTextStringWithinCursor + ( pos, str, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, cursorAcc, hr, hg, hb + ) = + if pos < String.size str then + case String.sub (str, pos) of + #" " => + (* if inside cursor, then create cursorAcc; + * else, just skip as usual *) + if absIdx <> cursorPos then + (* not in cursur *) + buildTextStringWithinCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + else + (* in cursor *) + let + val cursorAcc = buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g ,b) + in + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + | #"\t" => + (* todo: draw cursor if cursor is on tab + * but this is not a priority right now *) + buildTextStringWithinCursor + ( pos + 1, str, acc, posX + xSpace3, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + | #"\n" => + if posY + ySpace < windowHeight then + if absIdx <> cursorPos then + (* not in cursor position, so iterate like normal *) + buildTextStringWithinCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + else + (* in cursor position, so build cursorAcc + * and call AfterCursor function *) + if pos = String.size str - 1 andalso tl = [] then + (* if we are at end of lineGap, we want to build cursorAcc + * at different coordinates than usual *) + let + val cursorAcc = + buildCursor (startX, posY + ySpace, fWindowWidth, fWindowHeight, r, g, b) + in + accToDrawMsg (acc, cursorAcc) + end + else + let + val cursorAcc = buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g ,b) + in + buildTextStringAfterCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else + accToDrawMsg (acc, cursorAcc) + | #"\r" => + if posY + ySpace < windowHeight then + if + pos < String.size str - 1 + andalso String.sub (str, pos + 1) = #"\n" + then + buildTextStringWithinCursor + ( pos + 2, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 2, cursorPos, cursorAcc, hr, hg, hb + ) + else + buildTextStringWithinCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + else + accToDrawMsg (acc, cursorAcc) + | chr => + let + val chrFun = Vector.sub (CozetteAscii.asciiTable, Char.ord chr) + in + if absIdx <> cursorPos then + (* not equal to cursor *) + if posX + xSpace < windowWidth then + let + val chrVec = chrFun + (posX, posY, fontSize, fontSize, fWindowWidth, fWindowHeight, r, g, b) + val acc = chrVec :: acc + in + buildTextStringWithinCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + end + else if posY + ySpace < windowHeight then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , r, g, b + ) + val acc = chrVec :: acc + in + buildTextStringWithinCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + end + else + accToDrawMsg (acc, cursorAcc) + else + (* equal to cursor *) + let + val cursorAcc = buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g ,b) + in + if posX + xSpace < windowWidth then + let + val chrVec = chrFun + ( posX, posY, fontSize, fontSize + , fWindowWidth, fWindowHeight + , hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else if posY + ySpace < windowHeight then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextStringAfterCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else + accToDrawMsg (acc, cursorAcc) + end + end + + else + (* we have built cursor now, so can call after-cursor function + * to build rest of text *) + continueBuildTextLineGapAfterCursor + ( tl, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, cursorAcc + ) + + and continueBuildTextLineGapBeforeCursor + ( strList, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, absIdx, cursorPos, hr, hg, hb + ) = + case strList of + hd :: tl => + if cursorPos >= absIdx + cursorPos then + (* if end of string is before cursor *) + buildTextStringBeforeCursor + ( 0, hd, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, hr, hg, hb + ) + else + (* if within cursor *) + buildTextStringWithinCursor + ( 0, hd, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, [], hr, hg, hb + ) + | [] => accToDrawMsg (acc, []) + + fun build + (startLine, cursorPos, lineGap: LineGap.t, windowWidth, windowHeight) = + let + val lineGap = LineGap.goToLine (startLine, lineGap) + val {rightStrings, rightLines, line = curLine, idx = curIdx, ...} = lineGap + in + case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: _) => + let + (* get index of line to start building from *) + val startIdx = + if startLine > curLine then + let + val lnPos = startLine - curLine - 1 + val startIdx = Vector.sub (rLnHd, lnPos) + in + if + String.sub (rStrHd, startIdx) = #"\r" + andalso startIdx < String.size rStrHd - 1 + andalso String.sub (rStrHd, startIdx + 1) = #"\n" + then + (* handle \r\n pair *) + startIdx + 2 + else startIdx + 1 + end + else + 0 + + val absIdx = curIdx + startIdx + + in + if cursorPos < curIdx + String.size rStrHd then + (* if cursor is within string *) + buildTextStringWithinCursor + ( startIdx, rStrHd, [] + , 5, 5, 5 + , windowWidth, windowHeight + , Real32.fromInt windowWidth, Real32.fromInt windowHeight + , 0.67, 0.51, 0.83 + , rStrTl, absIdx, cursorPos, [] + , 0.211, 0.219, 0.25 + ) + else + (* if cursor is after string *) + buildTextStringBeforeCursor + ( startIdx, rStrHd, [] + , 5, 5, 5 + , windowWidth, windowHeight + , Real32.fromInt windowWidth, Real32.fromInt windowHeight + , 0.67, 0.51, 0.83 + , rStrTl, absIdx, cursorPos + , 0.211, 0.219, 0.25 + ) + end + | (_, _) => + (* requested line goes beyond the buffer, + * so just return empty list as there is nothig + * else we can do. *) + [] + end +end