Skip to content

Refactored cursor and selection manipulation, and more!#146

Merged
elcritch merged 3 commits into
elcritch:mainfrom
derek-v-s:input-mouse-selection
May 18, 2025
Merged

Refactored cursor and selection manipulation, and more!#146
elcritch merged 3 commits into
elcritch:mainfrom
derek-v-s:input-mouse-selection

Conversation

@derek-v-s

@derek-v-s derek-v-s commented May 17, 2025

Copy link
Copy Markdown
Collaborator

Issue #145

textboxes.nim

  • Implemented cursor + anchor model for selection
  • Refactored almost all cursor movement procedures into shiftCursor
  • Created an Orient enum with enumerators for shiftCursor (e.g. Left, NextWord, TheEnd)
  • Created placeCursor procedure for direct placement by index
  • Created clearSelection procedure
  • Created growSelection procedure
  • Rewrote delete and created deleteSelected
  • Rewrote insert, cursorUp, cursorDown, selection=, and findLine
  • Organized the cursor and selection procedures
  • Wrote documentation for various procedures
  • Changed NextWord / PreviousWord functionality to always jump to the beginning of each word

input.nim

  • Ensured the cursor blinks
  • Created activate() procedure
  • Rewrote delete-previous-word logic
  • Switched word skipping to Meta+Arrow-keys (this is much more common)
  • Switched word selection to Meta+Shift+Arrow-keys
  • Eliminated cursor alignment problem by connecting layoutResize slot

tinput.nim

  • Ensured input is activated, and the cursor is visible on init
  • Ensured cursor is placed at TheEnd on init

tinput2.nim

  • Ensured input is activated, and the cursor is visible on init
  • Ensured all numbers are editable

ttextboxes.nim

  • Updated tests based on new model / procedures

So I'm finally, mostly confident that everything works. It might be hard to believe I spent many hours every day for a full week on this. There's so much to understand and consider (as you already know).

There is, however, at least one remaining issue to deal with. Originally the insert procedure wouldn't allow any text to be added beyond what was originally set, when in Overwrite mode. That means it couldn't be used for the common insert/overwrite toggle feature. I've rewritten it to allow for that now, but that means that in tinput2 you can write numbers past the established range (00:00:00). So it seems like we need another option that can be set, such as LimitRange. Let me know if you have any ideas about that, and of course anything else in this PR.

Issue elcritch#145

textboxes.nim
- Implemented cursor + anchor model for selection
- Refactored almost all cursor movement procedurs into shiftCursor
- Created an Orient enum with enumerators for shiftCursor (e.g. Left,
NextWord, TheEnd)
- Created placeCursor procedure for direct placement by index
- Created clearSelection procedure
- Created growSelection procedure
- Rewrote delete and created deleteSelected
- Rewrote insert, cursorUp, cursorDown, `selection=`, and findLine
- Organized the cursor and selection procedures
- Wrote documentation for various procedures
- Change NextWord / PreviousWord functionality to always jump to the
beginning of each word

input.nim
- Ensured the cursor blinks
- Created activate() procedure
- Rewrote delete-previous-word logic
- Switched word skipping to Meta+Arrow-keys (this is much more common)
- Switched word selection to Meta+Shift+Arrow-keys
- Eliminated cursor alignment problem by connecting layoutResize slot

tinput.nim
- Ensured input is activated, and the cursor is visible on init
- Ensured cursor is placed at TheEnd on init

tinput2.nim
- Ensured input is activated, and the cursor is visible on init
- Ensured all numbers are editable

ttextboxes.nim
- Updated tests based on new model / procedures
@elcritch

Copy link
Copy Markdown
Owner

So I'm finally, mostly confident that everything works. It might be hard to believe I spent many hours every day for a full week on this. There's so much to understand and consider (as you already know).

Totally believable – I've spent so many hours dealing with subtle details with font layout, grid layout, events, etc. It's pretty rewarding though working on it at the fundamental level! But it can be a slog at times.

I'm looking forward to giving it a whirl!

There is, however, at least one remaining issue to deal with. Originally the insert procedure wouldn't allow any text to be added beyond what was originally set, when in Overwrite mode. That means it couldn't be used for the common insert/overwrite toggle feature.

Sweet! Did you happen to add tests for that as well? I wanna make sure we test those so refactoring or bug fixing is easier in the future. They're a pain but the LLMs are pretty good at writing them.

I've rewritten it to allow for that now, but that means that in tinput2 you can write numbers past the established range (00:00:00). So it seems like we need another option that can be set, such as LimitRange. Let me know if you have any ideas about that, and of course anything else in this PR.

Fine by me, and yeah generally we'll need some sort of configurable input filters. I think HTML input filters might be the best model.

It wasn't clear to me what the best way to add them would be. As you probably saw, there's the raw key inputs, and then input creates a second set of higher level events. That makes sense. What's unclear now is how best to have overrides that the end-user widget be can intercept and modify or cancel the high level events. Then again having a callback or regex could be another approach, as signals and slots don't have direct returns.

haha, yep you can see what I've been working on for a couple of years. I wanted to get the fundamentals set well, even if not perfect now so they can evolve. Also, trying to bridge the best between HTML and QT/GTK is an interesting challenge. It wasn't an original goal of Figuro but has evolved into that. ;)


if not connected(self, doKeyCommand, self, keyCommand):
connect(self, doKeyCommand, self, Input.keyCommand)
if not connected(self, doUpdateInput, self):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like moving this to initialize, though it goes back to the question of how best to add a filter. One way is this if not connected(self, doUpdateInput, ...) which would allow the user of Input to connect their own doUpdateInput handler first. I never followed through trying it out though, but did add the is connected logic in Sigils for this sort of scenario.

Dropping it for now makes sense though. Just wanted to point it out to you.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to ask about this. What's the difference between the initialize slot and using onInit in the draw slot?

I'm not clear about how the slot override by users works in terms of timing. Are you saying that connecting things in this initialize slot will block users from overriding those connections?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to ask about this. What's the difference between the initialize slot and using onInit in the draw slot?

Generally speaking initialize is used when implementing a widget, while onInit is for customizing an existing widget like Input when it's instantiated.

I added initialize first, but it's more hassle to add, so I added onInit as well. Technically, doInitialize & initialize are a signal and slot, while onInit is part of the the preDraw callback.

Hmmm, maybe I should make a diagram. I'm also thinking instead of preDraw calling that stage setup?

Widget Lifecycle

  1. Widget Allocated and preNode internal API runs
  2. Widget preDraw callback is called (e.g. Setup) -- this is what the user adds
  3. Widget doDraw signal emitted that calls draw and other slots.
  4. Widget postDraw callback is optionally called (unused currently)
  5. Widget destroy (not implemented yet, I think)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear about how the slot override by users works in terms of timing. Are you saying that connecting things in this initialize slot will block users from overriding those connections?

Hmmm, yes I think that would as initialize runs first. However, it's not clear what is the best way to override the doInput signals. I also experimented with a separate overrideInput or something.

Suggestions are welcome!

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this look?

https://github.com/elcritch/figuro/blob/f78059fcc0ae010376f9cd5b76cac6d5924791f2/README.md

All the details probably don't need to be there, but I don't know what's useful to you or others.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the preNode step refer to? And what's the condition were initialize is skipped? When the user has used onInit?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind asking that at #147 ?

Comment thread tests/tinput2.nim
this.updateInput(rune)
this.text.cursorNext()
onInit:
this.activate()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, so the onInit use cases is making sense now?

discard
elif down.matches({KAlt, KShift}):
case pressed.getKey
of KeyLeft:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, do we loose the alt-left alt-right handling of did you move it elsewhere? It'd need to be refactored to handle ctrl-left on windows/linux anyways.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word skipping and word selecting are Ctrl (Meta) based instead of Alt now. This is how things usually are set up on Windows/Linux. It's been a while since I've used macOS so I can't recall how it usually works on it.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, yeah MacOS uses alt. But that can be changed later.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok. Yea, this is something to have configurable by users anyhow.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok. Yea, this is something to have configurable by users anyhow.

Yeah, that'd be best. The current setup doesn't allow that aside from configuring Meta.

Comment thread src/figuro/ui/textboxes.nim Outdated
Comment thread src/figuro/widgets/input.nim Outdated
@elcritch

Copy link
Copy Markdown
Owner

haha, yep you can see what I've been working on for a couple of years. I wanted to get the fundamentals set well, even if not perfect now so they can evolve. Also, trying to bridge the best between HTML and QT/GTK is an interesting challenge. It wasn't an original goal of Figuro but has evolved into that. ;)

BTW to clarify this point, I mean the signals and events and core objects.

Being able to make it easy to use yet extensible is an interesting challenge. QT, GTK and the rest always seem to "hardcode" it into a big object hierarchy while HTML makes it almost impossible to customize. I also like Mac's Cocoa design where the input handler can be overriden at runtime. I used it to run Vim or Emacs plugins for years.

@elcritch elcritch merged commit e44eaad into elcritch:main May 18, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants