Eventcatcher Example: Simple Canvas

Hi @Rob_Jopling - I just tested on empty and it’s working fine for me, but in case it wasn’t clear, you can’t have them both loaded. The second one was changed structurally quite a bit and so they’re not compatible together.

Good point on the ability to edit, attached is a tweak where you can click on a note to open it, and ctrl-click on it to open in edit mode. I also changed the cursor to the point to make that more clear.

canvas 2025-11-18.json (19.0 KB)

Can this be made into a page template so that we can load the canvas layout as needed and the filter for the tiddlers to be displayed shall be dynamically changed using edit-text search box at the top

Brilliant @stobot

Thanks for that. And the latest version works fine.

Cheers, Rob

Still just an experiment / learning exercise, but added sizing and rotating by holding down a key while doing the dragging left and right, added a legend that shows when hovering over the canvas to show you the keys.

There are some issues - doesn’t like it when you go too fast, and if you drag outside of the canvas, it locks you in that mode until you click again. Not dealbreakers, but worth noting, relatively easy fixes.

arunnbabu81 - sounds possible, but outside the scope of what I’m playing with at the moment, I’d love to see you or someone pursue something like that though!

canvas-2025-11-19.json (21.3 KB)

Animation

8 Likes

Wow! Just wow.

(And still so clearly simple!)

1 Like

man, this is cool, i could see a lot of possibilities with this.
any chance of implementing transparency?

Really enjoying your work on this @stobot !

I tried quickly porting this to use the new $eventcatcher (part of the floats demo and expected to be a part of the TW v5.4.0 release) as it would benefit from the pointer capture support, alleviating problems areas like dragging too fast. I ran into an unexpected issue, currently the notes are completely redrawn when their position changes, due to the wrapping $let widget that declares a note-style variable, which releases the pointer capture.

Instead, I recommend directly assigning the style properties to the note div using the style.property syntax as follows, I have also updated the syntax to use the pointerCapture support in the new eventcatcher.:

\procedure canvas(tiddler:blank,filter:"[tag[Note]!is[draft]]",canvas-style:"",note-style:"",note-template:"<$transclude tiddler=<<currentTiddler>> mode='block'/>")
<$let width-default=200 height-default=200>
<div class="canvas" style=<<canvas-style>>>
<$list filter=<<filter>>>
<$eventcatcher selector=".canvas-note" $pointerdown="<<note-drag-start>>" $pointermove="<<note-drag-move>>" $pointerup="<<note-drag-end>>" pointerCapture="dynamic" $click="<<canvas-edit>>">
<div class={{{ [<tiddler>get[note]match<currentTiddler>then[canvas-note canvas-note-active]else[canvas-note]] }}}
  style.top=`${ [<currentTiddler>addsuffix[.y]] :map[<tiddler>getindex<currentTiddler>] }$px`
  style.left=`${ [<currentTiddler>addsuffix[.x]] :map[<tiddler>getindex<currentTiddler>] }$px` 
  style.width=`${ [<currentTiddler>addsuffix[.width]] :map[<tiddler>getindex<currentTiddler>else<width-default>] }$px` 
  style.rotate=`${ [<currentTiddler>addsuffix[.rotate]] :map[<tiddler>getindex<currentTiddler>else[0]] }$deg;$(note-style)$`
  style.height=`${ [<currentTiddler>addsuffix[.height]] :map[<tiddler>getindex<currentTiddler>else<height-default>] }$px`
>
<<note-template>>
</div>
</$eventcatcher>
</$list>
<div class="canvas-delete">
<%if [<tiddler>is[tiddler]] %>
<$button actions="<<canvas-delete>>" class="tc-btn-invisible">{{$:/core/images/delete-button}}</$button>
<%endif%>
</div>
<div class="canvas-help">
Clicking: Normal=Open, Ctrl=Edit | Dragging: Normal=Move, Shift=Size, Alt=Rotate
</div>
</div>
</$let>
\end

It would be especially helpful in terms of testing the pointer capture support if you wanted to play around with that upcoming version of eventcatcher.

2 Likes

Hi @Justin_H - in general I left the note-style property open so you could use any valid css. As an example, I think you’re talking about making the note background transparent? If so, here’s a screenshot of doing that using background-color:hsl(0 0 0 / 0). I also set the canvas to white to improve the contrast a bit and then gave it light border.

<<canvas canvas-style:"background-color:white;border:1px solid gainsboro;" note-style:"background-color:hsl(0 0 0 / 0);">>

Animation

If you meant applying transparency via dragging like move / size / rotate then look in the stobot/canvas/macros tiddler and you’ll see a pattern that you can adapt. I can give more detailed direction there if that’s what you intended, but I suspect not.

2 Likes

That’s awesome @saqimtiaz - I’ll definitely play around with it and report back. I didn’t know about the style.property syntax, I love how much cleaner it makes it than what I was having to do. The one thing I’m not sure about there is how I’d be able to arbitrarily just paste on the end the parameter string of note-style - I see you added it on the end of rotate? Is that valid? I was having problems so haven’t been able to test.

For some reason, moving the style onto the div directly made $mouseup trigger on every drag which it doesn’t do with the let statement (on 5.3.8). That was really annoying, so I just moved the $let inside the $eventcatcher for now until I test the new widget (“5.4.0”) you put together, maybe it doesn’t have that side effect with the new version.

thats awesome!

I was actually thinking about svgs or pngs to use as markers on a map or arrows, kinda like a dnd map or draw.io, but a tables a cool idea too, like custom statblocks.

theres so many cool ideas for this :nerd_face:

That was just a typo resulting from editing directly in the standard TW editor, something I am not used to. You should be able to use both the style.property syntax attributes and the style attribute together, just assign them directly to the div and not to a variable first.

mouseup or click? mouseup being triggered excessively was what happened with the new $eventcatcher due to the DIV being re-rendered every time its styles were updated. I can’t replicate that in v5.3.8. I do however get click being triggered at the end of every drag.

I would suggest avoiding the click event and replacing all mouse events with pointer equivalents. Then in pointerup check to see if a drag or resize has occurred and if it hasn’t, trigger the click actions.

Alternatively, use the matchSelector attribute of $eventcatcher to only trigger the resize/move actions when clicking a border/padding area around the note, and let the interior of the note receive click events.

This moves TiddlyWiki up a level.

I am thinking of docking to the left or right, top or bottom, so dragging an item and hitting the left border makes it occupy the left 50% of the container (canvas)!

1 Like

You’re right - I meant click in 5.3.8. I still haven’t spent time with the 5.4.0 yet, but I tried to simplify and replace the click with mouseup and it’s not working at the moment - I have to take a break on this for today.

This is as far as I got (NOT WORKING) just posting in case you see something obvious
stobot_canvas_macros.json (4.7 KB)

Ah.

That gives me an idea - I was thinking about arrows as well. I need to implement a way to have many version of the same tiddler, but with separate position, size, and rotation values stored in the canvas tiddler. Should be too hard, will think about the most elegant way to implement it. Right now, you’d have to have a unique tiddler for each arrow you’d want, I think I can overcome that.

1 Like

Hmm. I think detecting if you drag an item above the top or left would be relatively easy since I can just use the coordinates and look for x or y values below zero. I’m not sure how I’d get the right side since probably most of the time I’d want the canvas size to be 100% width, I don’t know if there’s a non-javascript way to figure that out in a value I can compare cursor position and width to. If anyone knows about that, let me know!

I don’t have the opportunity to test but my instinct suggests blank values from the state tiddler might be the issue, try this:

\procedure note-mouseup()
<%if [<tiddler>get[moved]!is[blank]else[no]!match[yes]] %>
  <$action-log/>
  <$action-navigate $to=<<currentTiddler>>/>
  <%if [<modifier>match[ctrl]] %>
    <$action-sendmessage $message="tm-edit-tiddler" $param=<<currentTiddler>>/>
  <%endif%>
<%endif%>
<$action-setfield 
  $tiddler=<<tiddler>> 
  action=""
  moved=""
/>
\end

Note that I threw in an action-log widget, so if this does not resolve it, check the variables logged.

1 Like

This is becoming a bit more complex, but implemented some small tweaks, bug fixes, and two big features - specifically around arrows like @Justin_H asked about. Still based on in 5.3.8 for now, though I’ll explore the new widget over the weekend.

The two big features are:

  • Note-level css override: If you want to style individual notes differently (like floating arrows)
  • Cloning notes: Once you have a note how you like it, clone it and then re-style (like rotate etc.)

canvas-2025-11-21.json (24.4 KB)

Demo: Building something like a process flow map out of tiddlers.

Note that the package now contains a tiddler named “arrow” that’s just a ascii arrow with font-size turned up. Works on my Windows machine, you can change to whatever you want though obviously

Step 1 (optional): Add a little overall styling and separate the 3 demo tiddlers. You might also notice that I added a default hover style which will come in handy later below.

<<canvas note-style:"box-shadow:0 0 6px;">>

Step1

Step 2: Add some individual styling (css below) to one specific note. I alt-click and an input box appears just below the canvas, I paste in the below and hit enter (or you can use the little emoji button on the far right).

display:flex;justify-content:center;align-items:center;box-shadow:0 0;background-color:hsl(0 0 0 / 0);

note-style

Step 3: Clone the arrow (as many times as you want) using ctrl-click. Note that ctrl-click used to be open in edit mode, I changed that to shift-click. This new note/clone is special, it has all of the same abilities as a regular note and it can be styled individually, but it lives in the canvas id tiddler (which is named blank by default). The content of the note continues to be linked to the original one though. To make it easy, I generate all clones in the top left.

note-clone

Step 4: Make and delete more clones! Did you notice that the hover color for the clones was green instead of red? That’s because clones can be deleted using shift-ctrl-click. Play around and clone & “unclone”.

note-unclone

This is getting pretty close to a usable plugin - I think I’m going to stabilize and clean it up and host it on TiddlyHost with better instructions. That’ll take me a bit though, adding features is always more fun!

6 Likes

I thought it’d be a fun exercise to do a slightly more complex demo using the same functionality, and highlighting that each canvas tiddler stores all of the parameters like position in a single tiddler. Because of that, you can save “states” and overwrite them to “reset” canvases.

For this demo I built a little Chess game. There’s a chessboard made with a table, and chess pieces made from unicode characters. This highlights the “cloning” as there’s a tiddler for one of each piece (on the left below), and then all the ones on the board are clones, so they can be deleted as the “game” progresses. Because of the “states”, there’s a tiddler called canvas/chess/ready that has everything setup, and then an exact copy is canvas/chess/play

The Chess tiddler just contains the macro pointed to canvas/chess/play. When you want to reset your game, clone canvas/chess/ready overtop of canvas/chess/play. Anyways, thought it was a fun way to illustrate a couple of the features. It does make me consider if I should add some pixel snapping capability to have things line up cleanly - optionally…

canvas-chess.json (24.9 KB)

canvas-chess

3 Likes

Thank you @stobot these are really fabulous demos, and deserve a wider audience.

2 Likes

@Christian_Byron the examples in this thread would merit a mention in the newsletter if that makes sense with the structure you have in mind.