Fix word-wrap toggle: actually stop wrapping when disabled

The bottom-bar 'Wrap: On/Off' toggle flipped State.WordWrap and relabelled
itself, but the render path never read the flag: NewTextField hardcoded
WordWrap: true and drawWrappedText always shaped at the region width with
WrapHeuristically, so lines wrapped in both modes.

NewTextField now takes the wordWrap flag, TextField.Draw passes tf.WordWrap
to drawWrappedText, and with wrap disabled the shaper gets unlimited width
(MaxWidth = maxInt32, as in single-line layout) so over-long lines extend
past the region and are clipped instead of wrapping. EditorLayout passes
its existing wordWrap parameter through.
This commit is contained in:
Greg Pomerantz 2026-08-19 22:35:13 -04:00
parent d8b5bc704b
commit eb725ee781
3 changed files with 12 additions and 5 deletions

View File

@ -2106,6 +2106,7 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element {
"editor_text",
visibleContent,
editorRegion,
wordWrap,
editorRegion.W,
visibleScrollOffset,
visibleCursorPos,

View File

@ -330,7 +330,7 @@ func (tf TextField) Draw(gtx layout.Context, r *Renderer) {
r.lastSelCaret = -1
r.lastIMEShowSeq = 0
}
r.drawWrappedText(gtx, tf.Value, tf.region, tf.WrapWidth, tf.ScrollOffset, tf.CursorPosition, tf.SelectionStart, tf.SelectionEnd, tf.CaretDrag)
r.drawWrappedText(gtx, tf.Value, tf.region, tf.WordWrap, tf.WrapWidth, tf.ScrollOffset, tf.CursorPosition, tf.SelectionStart, tf.SelectionEnd, tf.CaretDrag)
}
// runeCount returns the number of UTF-8 runes in s[:bytePos] (bytePos is a
@ -351,7 +351,7 @@ func runeCount(s string, bytePos int) int {
}
// NewTextField creates a visible multiline TextField.
func NewTextField(id string, value string, region Region, wrapWidth Dp, scrollOffset Dp, cursorPos int, selStart, selEnd int, interactions []Interaction) TextField {
func NewTextField(id string, value string, region Region, wordWrap bool, wrapWidth Dp, scrollOffset Dp, cursorPos int, selStart, selEnd int, interactions []Interaction) TextField {
return TextField{
id: id,
region: region,
@ -359,7 +359,7 @@ func NewTextField(id string, value string, region Region, wrapWidth Dp, scrollOf
interactions: interactions,
Value: value,
Multiline: true,
WordWrap: true,
WordWrap: wordWrap,
WrapWidth: wrapWidth,
ScrollOffset: scrollOffset,
CursorPosition: cursorPos,

View File

@ -750,7 +750,7 @@ func (r *Renderer) drawLine(gtx layout.Context, line []text.Glyph, x, y Dp, col
// detection via WrapHeuristically. Long words overflow the wrap width.
// Line spacing is fixed: LineHeight = fontSize × LineHeightScale, independent
// of glyph metrics. The shaper's first.Y accounts for line spacing.
func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, wrapWidth Dp, scrollOffset Dp, cursorPos, selStart, selEnd int, caretDrag bool) {
func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, wordWrap bool, wrapWidth Dp, scrollOffset Dp, cursorPos, selStart, selEnd int, caretDrag bool) {
if str == "" {
return
}
@ -770,10 +770,16 @@ func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, w
}
ascent := Dp(float32(r.theme.FontSize) * fontScale)
lineH := Dp(float32(lineHeightSp) * fontScale)
// Wrap disabled: shape with unlimited width so lines extend past the
// region (clipped by textClip below) instead of wrapping.
maxWidthPx := maxInt32
if wordWrap {
maxWidthPx = int(r.toPx(wrapWidth))
}
params := text.Parameters{
PxPerEm: fixed.I(gtx.Sp(r.theme.FontSize)),
MinWidth: 0,
MaxWidth: int(r.toPx(wrapWidth)),
MaxWidth: maxWidthPx,
MaxLines: 0, // unlimited - wrap at MaxWidth
LineHeight: fixed.I(gtx.Sp(lineHeightSp)),
LineHeightScale: 1.0, // use LineHeight directly, don't scale