From 373ec8b36bf51cc5e4d9ba50b06f4aa6b0725d73 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Fri, 13 Feb 2026 22:32:45 -0700 Subject: [PATCH] scrollable now supports horizontal scrolling. using it in os wm. putting all os stuff in os namespace --- src/cg/03-ui-components.cg | 25 ++- src/cg/05-palette.cg | 1 + src/cg/10-os.cg | 398 +++++++++++++++++++------------------ 3 files changed, 222 insertions(+), 202 deletions(-) diff --git a/src/cg/03-ui-components.cg b/src/cg/03-ui-components.cg index 88463ae..66ec996 100644 --- a/src/cg/03-ui-components.cg +++ b/src/cg/03-ui-components.cg @@ -26,25 +26,36 @@ button = config \ # scrollable scrollable = config \ - barHeight = max 20 (config.h * config.h / config.totalHeight); - barY = config.scrollY * config.h / config.totalHeight; - showBar = config.totalHeight > config.h; + showVBar = config.totalHeight > config.h; + vBarHeight = max 20 (config.h * config.h / config.totalHeight); + vBarY = config.scrollY * config.h / config.totalHeight; + + showHBar = config.totalWidth > config.w; + vBarWidth = max 20 (config.w * config.w / config.totalWidth); + vBarX = config.scrollX * config.w / config.totalWidth; ui.stack { children = [ ui.scrollable { w = config.w, h = config.h, - scrollX = 0, + scrollX = config.scrollX, scrollY = config.scrollY, onScroll = config.onScroll, child = config.child }, - ...(showBar + ...(showVBar | True \ [ui.positioned { x = config.w - 4, - y = barY, - child = ui.rect { w = 4, h = barHeight, color = "rgba(255,255,255,0.3)", radius = 2 } + y = vBarY, + child = ui.rect { w = 4, h = vBarHeight, color = "rgba(255,255,255,0.3)", radius = 2 } + }] + | False \ []), + ...(showHBar + | True \ [ui.positioned { + x = config.h - 4, + y = hBarX, + child = ui.rect { h = 4, w = hBarWidth, color = "rgba(255,255,255,0.3)", radius = 2 } }] | False \ []) ] diff --git a/src/cg/05-palette.cg b/src/cg/05-palette.cg index 885a538..a5ebc3e 100644 --- a/src/cg/05-palette.cg +++ b/src/cg/05-palette.cg @@ -126,6 +126,7 @@ palette = config \ w = contentWidth, h = listHeight, totalHeight = totalHeight, + totalWidth = contentWidth, scrollX = 0, scrollY = paletteState.scrollOffset, onScroll = onScroll, diff --git a/src/cg/10-os.cg b/src/cg/10-os.cg index 401f913..0fc20d8 100644 --- a/src/cg/10-os.cg +++ b/src/cg/10-os.cg @@ -9,204 +9,212 @@ osState = { nextId = 0 }; -openPalette = _ \ osState.palette.visible := not (osState.palette.visible); - -windowWidth = window \ window.fullWidth - | True \ viewport.width - | False \ window.width; - -openWindow = title content \ - id = osState.nextId; - batch [ - osState.nextId := id + 1, - osState.windows := osState.windows & [{ - id = id, - title = title, - content = content, - width = osState.wm.defaultWindowWidth, - fullWidth = False - }], - osState.palette.visible := False, - focusWindow (len osState.windows) - ]; - -closeWindowByIndex = i \ - focused = osState.wm.focusedIndex; - newFocused = (i < focused - | True \ focused - 1 - | False \ (i == focused - | True \ max 0 (focused - 1) - | False \ focused)); - - batch [ - osState.windows := (take i osState.windows) & (drop (i + 1) osState.windows), - focusWindow newFocused - # osState.wm.focusedIndex := min newFocused (len osState.windows - 2) - ]; - -closeFocusedWindow = _ \ closeWindowByIndex osState.wm.focusedIndex; - -closeWindowById = id \ - i = index id (map (w \ w.id) osState.windows); - closeWindowByIndex (unwrapOr 0 i); - -toggleMaximizeFocusedWindow = _ \ - idx = osState.wm.focusedIndex; - osState.windows := updateAt idx (w \ w.{ fullWidth = not w.fullWidth }) osState.windows; - -scrollToWindow = index \ - windows = osState.windows; - widths = map windowWidth windows; - gap = 1; - windowX = (sum (take index widths)) + (index * gap); - windowW = unwrapOr osState.wm.defaultWindowWidth (nth index widths); - scrollOffset = osState.wm.scrollOffset; - vw = viewport.width; - (windowX < scrollOffset - | True \ windowX - | False \ (windowX + windowW > scrollOffset + vw - | True \ windowX + windowW - vw - | False \ scrollOffset)); - -focusWindow = index \ - batch [ - osState.wm.focusedIndex := index, - osState.wm.scrollOffset := scrollToWindow index - ]; - -onSelect = input \ - result = eval input; - result - | Value v \ openWindow input (_ \ ui.text { content = show v, color = "white" }) - | Defined name \ openWindow name (_ \ inspector { name = name }) - | Err msg \ openWindow "Error" (_ \ ui.text { content = msg, color = "red" }); - -renderWindow = window isActive \ - titleBarHeight = 30; - - ui.stack { - children = [ - # background - ui.rect { w = (windowWidth window), h = viewport.height, color = (isActive | True \ "#0a2a3a" | False \ "#061820")}, - - # title bar - ui.positioned { - x = 0, y = 0, - child = ui.stack { - children = [ - # background - ui.rect { w = (windowWidth window), h = titleBarHeight, color = (isActive | True \ "#a15f80" | False \ "#0f3348") }, - ui.row { - children = [ - # close button - ui.clickable { - onClick = _ \ closeWindowById window.id, - child = ui.stack { - children = [ - # button background - ui.rect { w = titleBarHeight, h = titleBarHeight, color = "rgba(255,255,255,0.2)" }, - # button text - ui.positioned { - x = 9, y = 7, child = ui.text { content = "x" } - } - ] - } - }, - - # title - ui.positioned { x = 8, y = 8, child = ui.text { content = window.title, color = "white" } }, - ] - } - ] - } - }, - - # content - ui.positioned { - x = 0, y = titleBarHeight, - child = ui.clip { - w = (windowWidth window), - h = viewport.height - titleBarHeight, - child = window.content 0 - } - } - ] - }; +os = + openPalette = _ \ osState.palette.visible := not (osState.palette.visible); + + windowWidth = window \ window.fullWidth + | True \ viewport.width + | False \ window.width; + + totalWidth = (sum (map windowWidth osState.windows)) + (len osState.windows - 1); + + scrollToWindow = index \ + windows = osState.windows; + widths = map windowWidth windows; + gap = 1; + windowX = (sum (take index widths)) + (index * gap); + windowW = unwrapOr osState.wm.defaultWindowWidth (nth index widths); + scrollOffset = osState.wm.scrollOffset; + vw = viewport.width; + (windowX < scrollOffset + | True \ windowX + | False \ (windowX + windowW > scrollOffset + vw + | True \ windowX + windowW - vw + | False \ scrollOffset)); + + focusWindow = index \ + batch [ + osState.wm.focusedIndex := index, + osState.wm.scrollOffset := scrollToWindow index + ]; + + openWindow = title content \ + id = osState.nextId; + batch [ + osState.nextId := id + 1, + osState.windows := osState.windows & [{ + id = id, + title = title, + content = content, + width = osState.wm.defaultWindowWidth, + fullWidth = False + }], + osState.palette.visible := False, + focusWindow (len osState.windows) + ]; + + closeWindowByIndex = i \ + focused = osState.wm.focusedIndex; + newFocused = (i < focused + | True \ focused - 1 + | False \ (i == focused + | True \ max 0 (focused - 1) + | False \ focused)); + + batch [ + osState.windows := (take i osState.windows) & (drop (i + 1) osState.windows), + focusWindow newFocused + # osState.wm.focusedIndex := min newFocused (len osState.windows - 2) + ]; + + closeFocusedWindow = _ \ closeWindowByIndex osState.wm.focusedIndex; + + closeWindowById = id \ + i = index id (map (w \ w.id) osState.windows); + closeWindowByIndex (unwrapOr 0 i); + + toggleMaximizeFocusedWindow = _ \ + idx = osState.wm.focusedIndex; + osState.windows := updateAt idx (w \ w.{ fullWidth = not w.fullWidth }) osState.windows; + + renderWindow = window isActive \ + titleBarHeight = 30; -windowComponent = config \ ui.stateful { - key = "window-" & (show config.index), - focusable = True, - init = {}, - update = state event \ event - | Focused \ { state = state, emit = [focusWindow config.index] } - | ChildFocused \ { state = state, emit = [focusWindow config.index] } - | _ \ { state = state, emit = [] }, - view = state \ renderWindow config.window config.isActive -}; - -renderWindows = _ \ - windows = osState.windows; - focused = osState.wm.focusedIndex; - scrollable { - w = viewport.width, - h = viewport.height, - scrollX = osState.wm.scrollOffset, - scrollY = 0, - child = ui.row { - gap = 1, - children = mapWithIndex (w i \ windowComponent { window = w, index = i, isActive = (i == focused) }) windows - } - }; - -search = q \ - storeRes = storeSearch q; - [ - Section "STORE", - ...map (name \ Item { label = name }) storeRes, - Section "EVAL", - Item { label = q } - ]; - -os = ui.stateful { - key = "os", - autoFocus = True, - - init = {}, - - update = state event \ event - | Key { key = "p", meta = True } \ - { state = state, emit = [openPalette] } - | Key { key = "ArrowLeft", meta = True } \ - ( - newIndex = max 0 (osState.wm.focusedIndex - 1); - { state = state, emit = [focusWindow newIndex] } - ) - | Key { key = "ArrowRight", meta = True } \ - ( - newIndex = min (len osState.windows - 1) (osState.wm.focusedIndex + 1); - { state = state, emit = [focusWindow newIndex] } - ) - | Key { key = "d", meta = True } \ - { state = state, emit = [closeFocusedWindow 0] } - | Key { key = "f", meta = True } \ - { state = state, emit = [toggleMaximizeFocusedWindow 0] } - | _ \ { state = state, emit = [] }, - - view = state \ ui.stack { children = [ - ui.rect { w = viewport.width, h = viewport.height, color = "#012" }, - - renderWindows 0, - - # keep palette at the end so it's on top - osState.palette.visible - | True \ palette { - search = search, - onSelect = onSelect, - viewport = viewport, + # background + ui.rect { w = (windowWidth window), h = viewport.height, color = (isActive | True \ "#0a2a3a" | False \ "#061820")}, + + # title bar + ui.positioned { + x = 0, y = 0, + child = ui.stack { + children = [ + # background + ui.rect { w = (windowWidth window), h = titleBarHeight, color = (isActive | True \ "#a15f80" | False \ "#0f3348") }, + ui.row { + children = [ + # close button + ui.clickable { + onClick = _ \ closeWindowById window.id, + child = ui.stack { + children = [ + # button background + ui.rect { w = titleBarHeight, h = titleBarHeight, color = "rgba(255,255,255,0.2)" }, + # button text + ui.positioned { + x = 9, y = 7, child = ui.text { content = "x" } + } + ] + } + }, + + # title + ui.positioned { x = 8, y = 8, child = ui.text { content = window.title, color = "white" } }, + ] + } + ] } - | False \ empty, + }, + + # content + ui.positioned { + x = 0, y = titleBarHeight, + child = ui.clip { + w = (windowWidth window), + h = viewport.height - titleBarHeight, + child = window.content 0 + } + } ] - } + }; + + windowComponent = config \ ui.stateful { + key = "window-" & (show config.index), + focusable = True, + init = {}, + update = state event \ event + | Focused \ { state = state, emit = [focusWindow config.index] } + | ChildFocused \ { state = state, emit = [focusWindow config.index] } + | _ \ { state = state, emit = [] }, + view = state \ renderWindow config.window config.isActive }; + + renderWindows = _ \ + windows = osState.windows; + focused = osState.wm.focusedIndex; + totalHeight = viewport.height; + scrollable { + w = viewport.width, + h = viewport.height, + scrollX = osState.wm.scrollOffset, + scrollY = 0, + onScroll = onScroll, + child = ui.row { + gap = 1, + children = mapWithIndex (w i \ windowComponent { window = w, index = i, isActive = (i == focused) }) windows + }, + }; + + search = q \ + storeRes = storeSearch q; + [ + Section "STORE", + ...map (name \ Item { label = name }) storeRes, + Section "EVAL", + Item { label = q } + ]; + + onScroll = delta \ + osState.wm.scrollOffset := max 0 (min (totalWidth - viewport.width) (osState.wm.scrollOffset + delta.deltaX)); + + onSelect = input \ + result = eval input; + result + | Value v \ openWindow input (_ \ ui.text { content = show v, color = "white" }) + | Defined name \ openWindow name (_ \ inspector { name = name }) + | Err msg \ openWindow "Error" (_ \ ui.text { content = msg, color = "red" }); + + ui.stateful { + key = "os", + autoFocus = True, + + init = {}, + + update = state event \ event + | Key { key = "p", meta = True } \ + { state = state, emit = [openPalette] } + | Key { key = "ArrowLeft", meta = True } \ + ( + newIndex = max 0 (osState.wm.focusedIndex - 1); + { state = state, emit = [focusWindow newIndex] } + ) + | Key { key = "ArrowRight", meta = True } \ + ( + newIndex = min (len osState.windows - 1) (osState.wm.focusedIndex + 1); + { state = state, emit = [focusWindow newIndex] } + ) + | Key { key = "d", meta = True } \ + { state = state, emit = [closeFocusedWindow 0] } + | Key { key = "f", meta = True } \ + { state = state, emit = [toggleMaximizeFocusedWindow 0] } + | _ \ { state = state, emit = [] }, + + view = state \ + ui.stack { + children = [ + ui.rect { w = viewport.width, h = viewport.height, color = "#012" }, + + renderWindows 0, + + # keep palette at the end so it's on top + osState.palette.visible + | True \ palette { + search = search, + onSelect = onSelect, + viewport = viewport, + } + | False \ empty, + ] + } + };