2024-06-01 Fonts are important ============================== I was thinking about writing a simple editor again. The Small and Nearly Silent web app by @eli_oat@tenforward.social is beautiful because it mixes text and line drawings. I often feel that I'd like a simple path-based SVG-like drawing ability, like lines, by @akkartik@merveilles.town. Some lines, some control points, some curves, filling stuff with patterns… I don't know whether I'd actually use it, but it's alluring. The whole uxn ecosystem with many different small tools is also quite alluring. Unfortunately, it's also a bit hard to get into an existing ecosystem like that. I still feel that if spent a while writing something myself, it'd feel more like it was "mine". I'd just understand it better. I just don't know if I'm ready to spend that much time on it, or if I'd even be able to pull it off. So that's why I wavering at the same time that I'm fascinated. Given all that, I was thinking about Gforth and found SDL2 Bindings for Gforth, and I found the example Yellow Snow game using this library, I tried to compile it and Gforth complained about the missing headers. No problem, I thought to myself. I know what to do: sudo apt install libsdl2-ttf-dev libsdl2-image-dev libsdl2-mixer-dev – but the mixer results in SuperCollider getting deinstalled. Oh no! So now I'm once again thinking about the thing I actually want to do. Do I want to build an Emacs light? A simple line-based text editor like ed but with more features? An editor that only runs inside a terminal, like kilo or mini? See 2018-03-12 Writing an Editor for more. Or would it be an editor with a GUI? Would it work only with X11? Or would it be based on SDL? I feel like an editor with a minimal GUI would be nice. I don't think I need a menu, but I think I'd like a way to scale font size up and down, UTF-8 support to write German and Portuguese, basically render Markdown and some simple variant of SVG, inline. I feel like that's why I need to start with graphics and font support. Otherwise I'll end up with something like grid: a cool ACME-inspired text editor, but it would only really works for English. #Editor 2024-06-04. Thinking about Go and the mini editor. It runs in a terminal. So what would it take to turn it into a graphical editor with fonts, ligatures, bidirectional text, font-sizes, and all of that? go-text/typesetting is a library that's used by Fyne, Gio, and Ebitengine. So perhaps one of these frameworks? Fyne has a Notes example application. It looks like a nice, simple app. When I built it, however, it didn't work. Is it because I'm on Wayland? I don't know. Gio is used by Anvil, an ACME-like editor with many features. In fact, it already looks like it has too many features for me to start building something. I wonder. Perhaps those are the right features? I just can't warm up to using the mouse all that much. Ebitengine has a Font example. It just shows how one loads a font for a number of code-points and that's that. An interesting way to load a font, for sure. 2024-06-07. As first step towards 100% keyboard navigation for Anvil, I thought about implementing MoveUp, MoveDown, MoveLeft and MoveRight. I thought about binding them to Alt and the left arrow keys, or Alt and h j k l. Next up would be commands to move to the editor, to the window tag, the column tag, or the editor tag. (I'm a bit unsure about the naming conventions, here.) From aa48c2de7a1337a73b7d500ebe037c204a13e748 Mon Sep 17 00:00:00 2001 From: Alex Schroeder <alex@gnu.org> Date: Fri, 7 Jun 2024 21:16:05 +0200 Subject: [PATCH] Add movement commands --- anvil/src/anvil/cmds.go | 54 +++++++++++++++++++++++++++++++++++++++ anvil/src/anvil/col.go | 46 +++++++++++++++++++++++++++++++++ anvil/src/anvil/window.go | 30 ++++++++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/anvil/src/anvil/cmds.go b/anvil/src/anvil/cmds.go index 965a76f..29fef85 100644 --- a/anvil/src/anvil/cmds.go +++ b/anvil/src/anvil/cmds.go @@ -115,6 +115,10 @@ func (c *CommandExecutor) initToplevelCommands() { addCommand("Goto", c.CmdGoto, "Jump to a bookmark", "Goto sets the current cursor position in the window body to the named bookmark, created by Mark. If no argument is given it jumps to the bookmark 'def'.") addCommand("Marks", c.CmdMarks, "Display bookmarks", "Marks displays the currently set bookmarks to the Errors window.") addCommand("Marks-", c.CmdClearMarks, "Clear bookmarks", "Marks- clears all the currently set bookmarks.") + addCommand("MoveUp", c.CmdMoveUp, "Move up", "Move to the window below") + addCommand("MoveDown", c.CmdMoveDown, "Move down", "Move to the window above") + addCommand("MoveLeft", c.CmdMoveLeft, "Move left", "Move to the column on the left") + addCommand("MoveRight", c.CmdMoveRight, "Move right", "Move to the column on the right") addCommand("SaveStyle", c.CmdSaveStyle, "Save current editor style", fmt.Sprintf("SaveStyle saves the editor style information to a file: the current font and size, colors, etc. With one argument the style is saved to the file named by the argument. With no argument it is saved to %s. When the editor is started the style file %s is loaded", StyleConfigFile(), StyleConfigFile())) addCommand("LoadStyle", c.CmdLoadStyle, "Load editor style from file", fmt.Sprintf("LoadStyle loads the editor style information from a file: the current font and size, colors, etc. With one argument the style is loaded from the file named by the argument. With no argument it is loaded from %s. When the editor is started the style file %s is loaded", StyleConfigFile(), StyleConfigFile())) addCommand("LoadPlumbing", c.CmdLoadPlumbing, "Load plumbing rules from file", fmt.Sprintf("LoadPlumbing loads the plumbing rules from a file. With one argument the plumbing is loaded from the file named by the argument. With no argument it is loaded from %s. When the editor is started the plumbing file %s is loaded", PlumbingConfigFile(), PlumbingConfigFile())) @@ -1407,6 +1411,56 @@ func (c CommandExecutor) CmdInsertLozenge(ctx *CmdContext) { } } +func (c CommandExecutor) CmdMoveUp(ctx *CmdContext) { + switch v := c.source.(type) { + case Window: + case *Window: + w := v.above() + if (w != nil) { + w.SetFocus(ctx.Gtx) + } + } +} + +func (c CommandExecutor) CmdMoveDown(ctx *CmdContext) { + switch v := c.source.(type) { + case Window: + case *Window: + w := v.below() + if (w != nil) { + w.SetFocus(ctx.Gtx) + } + } +} + +func (c CommandExecutor) CmdMoveLeft(ctx *CmdContext) { + switch v := c.source.(type) { + case Window: + case *Window: + col := v.col.left() + if (col != nil) { + w := col.windowAt(v.TopY) + if (w != nil) { + w.SetFocus(ctx.Gtx) + } + } + } +} + +func (c CommandExecutor) CmdMoveRight(ctx *CmdContext) { + switch v := c.source.(type) { + case Window: + case *Window: + col := v.col.right() + if (col != nil) { + w := col.windowAt(v.TopY) + if (w != nil) { + w.SetFocus(ctx.Gtx) + } + } + } +} + func (c CommandExecutor) CmdHelp(ctx *CmdContext) { if len(ctx.Args) > 0 { diff --git a/anvil/src/anvil/col.go b/anvil/src/anvil/col.go index 4059fec..1231007 100644 --- a/anvil/src/anvil/col.go +++ b/anvil/src/anvil/col.go @@ -541,3 +541,49 @@ func (c *Col) Name() string { func (c *Col) hasNoUserSetName() bool { return strings.HasPrefix(c.Tag.String(), "New") } + +// The column to the left of the given column. +// May return nil. +func (c *Col) left() *Col { + var col *Col + left := -1 + for _, otherCol := range editor.Cols { + if otherCol.LeftX < c.LeftX && + (left == -1 || otherCol.LeftX > left) { + col = otherCol + left = otherCol.LeftX + } + } + return col +} + +// The column to the right of the given column. +// May return nil. +func (c *Col) right() *Col { + var col *Col + left := -1 + for _, otherCol := range editor.Cols { + if otherCol.LeftX > c.LeftX && + (left == -1 || otherCol.LeftX < left) { + col = otherCol + left = otherCol.LeftX + } + } + return col +} + +// The window in this column at approximately this position. +// May return nil if the column has no windows. +func (c *Col) windowAt(y int) *Window { + var win *Window; + top := -1 + for _, otherWin := range c.Windows { + // Use <= because the other window might be at the exact same Y. + if otherWin.TopY <= y && + (top == -1 || otherWin.TopY > top) { + win = otherWin + top = otherWin.TopY + } + } + return win +} diff --git a/anvil/src/anvil/window.go b/anvil/src/anvil/window.go index b5a5591..7646e1f 100644 --- a/anvil/src/anvil/window.go +++ b/anvil/src/anvil/window.go @@ -799,3 +799,33 @@ loop: } } } + +// Return the window above this one. +// May return nil. +func (w *Window) above() *Window { + var win *Window + top := -1 + for _, otherWin := range w.col.Windows { + if otherWin.TopY < w.TopY && + (top == -1 || otherWin.TopY > top) { + win = otherWin + top = otherWin.TopY + } + } + return win +} + +// Return the window below this one. +// May return nil. +func (w *Window) below() *Window { + var win *Window + top := -1 + for _, otherWin := range w.col.Windows { + if otherWin.TopY > w.TopY && + (top == -1 || otherWin.TopY < top) { + win = otherWin + top = otherWin.TopY + } + } + return win +} -- 2.39.2