Class: Teek::UI::Handle
- Inherits:
-
Object
- Object
- Teek::UI::Handle
- Defined in:
- lib/teek/ui/handle.rb
Overview
The single handle type for a node, valid across both phases (Resolved
decision #3 in the architecture doc - no separate build-time NodeRef).
During build you compose/name/record-events on it; live methods
(#path, #configure) raise NotRealizedError until the node's
realized slot is filled in by the realizer, then the same Handle
object drives the real widget through it.
Constant Summary collapse
- RIGHT_CLICK_EVENTS =
MouseEvents::RIGHT_CLICK_EVENTS
- MENU_HANDLE_TYPES =
MouseEvents::MENU_HANDLE_TYPES
Instance Method Summary collapse
-
#app ⇒ Teek::App
The underlying app this widget was realized into.
-
#arc(*coords, **opts) ⇒ CanvasItem
An arc/pie-slice/chord along the oval inscribed in the bounding box
[x1, y1, x2, y2]. -
#bitmap(*coords, **opts) ⇒ CanvasItem
A stipple bitmap anchored at
[x, y]. -
#configure(**opts) ⇒ Object
Mutate the live widget's (or, for a type with none of its own, e.g. a menu entry - the entry's own) options - delegated entirely to this node type's WidgetType#addressing strategy, so Handle itself carries no per-type knowledge of how to reach it.
-
#destroy!(defer: nil) ⇒ nil
Tears down this node's live widget (and everything under it), releasing its callbacks via teek's existing
<Destroy>cleanup, and resets it so a later push of a fresh mount rebuilds it from scratch. -
#disable ⇒ self
Shorthand for
configure(state: :disabled)- greyed out, not interactive/invocable. -
#ellipse(*coords, **opts) ⇒ CanvasItem
(also: #oval)
An ellipse inscribed in the bounding box
[x1, y1, x2, y2]. -
#enable ⇒ self
Shorthand for
configure(state: :normal)- Tk's own default state, meaningful for anything with a-stateoption (a menu entry, a ttk widget, ...). -
#events ⇒ Array<EventBinding>
Every event binding declared on this node so far, in declaration order - +on_click+/+on_key+/+on_drag+/+on_right_click+ and friends all funnel through here.
-
#hide ⇒ self
Hide the window: releases any grab #show set (a no-op if it wasn't modal - Window#grab_release is always safe to call) and withdraws it.
-
#initialize(node) ⇒ Handle
constructor
private
A new instance of Handle.
-
#line(*coords, **opts) ⇒ CanvasItem
A straight line through the given points -
[x1, y1, x2, y2, ...], flat or nested, two or more points. -
#modal(global: false) { ... } ⇒ void
Show the window modally: grabs input and sets focus on it immediately.
-
#name ⇒ Symbol?
The node's explicit name.
-
#on_click { ... } ⇒ self
Fires on a left click.
-
#on_close { ... } ⇒ self
Fires when the window's close button (titlebar close box, Cmd-W, Alt-F4, ...) is pressed.
-
#on_drag {|x, y| ... } ⇒ self
Fires while dragging (left button held down and moving).
-
#on_key(spec) { ... } ⇒ self
Fires on a key press.
-
#on_right_click(menu = nil) { ... } ⇒ self
Fires on a right click, however the platform spells it (Button-3 on Linux/Windows, Button-2 or Control-Button-1 on macOS).
-
#on_tab_changed {|name_or_index| ... } ⇒ self
Fires when the selected tab changes (Tk's <
>). -
#options ⇒ Hash{Symbol => String}
What Tk currently thinks this widget's (or, for a type with none of its own, e.g. a menu entry - the entry's own) options are right now, straight from a bare
configure- for when a prior #configure call seems like it silently didn't take, or just exploring what's actually set on a live widget. -
#path ⇒ String
This node's live address - the real Tk widget path for an ordinary widget, or (for a type with none of its own, e.g. a menu entry) its WidgetType#addressing strategy's own marked, Tk-path-shaped virtual path - see MenuEntryAddressing#virtual_path.
-
#polygon(*coords, **opts) ⇒ CanvasItem
A closed shape through the given points -
[x1, y1, x2, y2, ...], flat or nested, three or more points. -
#rectangle(*coords, **opts) ⇒ CanvasItem
A rectangle with corners
[x1, y1, x2, y2]. -
#release_focus ⇒ void
(also: #grab_release)
Release a grab previously set with #modal - "grab" is X11/Tk jargon for capturing all input to one window, which is what a modal dialog is doing while it's up.
-
#show ⇒ self
Reveal the window: positions it just to the right of the parent it's nested under (root, or another window if this one's nested inside it), deiconifies, raises it to the front, and - only if this window was declared
modal: true- grabs input and focuses it too (via #modal). -
#tagged(tag) ⇒ CanvasItem
A handle onto whatever items currently carry
tag- zero, one, or many (see CanvasItem, which addresses a tag and an id identically). -
#text(*coords, **opts) ⇒ CanvasItem
Text anchored at
[x, y]. -
#text_content ⇒ TextContent
The rich text API for this widget's content - insert/get/delete, named formats (Tk's own "tag" concept), markers, search, and embedded images.
-
#type ⇒ Symbol
The node's type, e.g.
Constructor Details
#initialize(node) ⇒ Handle
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of Handle.
27 28 29 30 |
# File 'lib/teek/ui/handle.rb', line 27 def initialize(node) @node = node @addressing = (WidgetTypes.for_type(node.type)&.addressing || WidgetAddressing).new(node) end |
Instance Method Details
#app ⇒ Teek::App
Returns the underlying app this widget was realized into.
54 55 56 |
# File 'lib/teek/ui/handle.rb', line 54 def app realized.app end |
#arc(*coords, **opts) ⇒ CanvasItem
An arc/pie-slice/chord along the oval inscribed in the bounding
box [x1, y1, x2, y2]. Only valid on a ui.canvas handle.
398 399 400 |
# File 'lib/teek/ui/handle.rb', line 398 def arc(*coords, **opts) create_canvas_item(:arc, coords, opts) end |
#bitmap(*coords, **opts) ⇒ CanvasItem
A stipple bitmap anchored at [x, y]. Only valid on a ui.canvas handle.
408 409 410 |
# File 'lib/teek/ui/handle.rb', line 408 def bitmap(*coords, **opts) create_canvas_item(:bitmap, coords, opts) end |
#configure(**opts) ⇒ Object
Mutate the live widget's (or, for a type with none of its own, e.g. a menu entry - the entry's own) options - delegated entirely to this node type's WidgetType#addressing strategy, so Handle itself carries no per-type knowledge of how to reach it.
64 65 66 |
# File 'lib/teek/ui/handle.rb', line 64 def configure(**opts) @addressing.configure(**opts) end |
#destroy!(defer: nil) ⇒ nil
Tears down this node's live widget (and everything under it),
releasing its callbacks via teek's existing <Destroy> cleanup,
and resets it so a later push of a fresh mount rebuilds it from
scratch. The rebuild gets a fresh Tk path, not necessarily this
same one - path segments are claimed once and never recycled (see
Document#claim_path_segment), so this stays safe even if another
instance sharing this same local name is still alive elsewhere
under the same parent. Typically called after popping a screen you
don't want to keep warm (see Screens) - popping alone only
conceals, exactly as before; this is a separate, explicit step -
+ui.screens.pop&.destroy!+/+ui.modal.pop&.destroy!+.
Destroying a widget SYNCHRONOUSLY from inside the click handler
of one of its own descendants (a dialog's own "Close" button
tearing down the dialog it lives in) is a real Tk hazard:
ttk::button (and others) queue their own internal bindings for
that SAME click, which then run against a widget that's already
gone. defer absorbs this automatically - no need to know about
it or reach for ui.after yourself.
140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/teek/ui/handle.rb', line 140 def destroy!(defer: nil) return nil if @node.pending_destroy? should_defer = defer.nil? ? Teek.in_callback? : defer if should_defer app = realized.app @node.pending_destroy = true app.after_idle { perform_destroy! } else perform_destroy! end nil end |
#disable ⇒ self
Shorthand for configure(state: :disabled) - greyed out, not
interactive/invocable.
94 95 96 97 |
# File 'lib/teek/ui/handle.rb', line 94 def disable configure(state: :disabled) self end |
#ellipse(*coords, **opts) ⇒ CanvasItem Also known as: oval
An ellipse inscribed in the bounding box [x1, y1, x2, y2]. Only
valid on a ui.canvas handle.
354 355 356 |
# File 'lib/teek/ui/handle.rb', line 354 def ellipse(*coords, **opts) create_canvas_item(:oval, coords, opts) end |
#enable ⇒ self
Shorthand for configure(state: :normal) - Tk's own default
state, meaningful for anything with a -state option (a menu
entry, a ttk widget, ...).
85 86 87 88 |
# File 'lib/teek/ui/handle.rb', line 85 def enable configure(state: :normal) self end |
#events ⇒ Array<EventBinding>
Every event binding declared on this node so far, in declaration
order - +on_click+/+on_key+/+on_drag+/+on_right_click+ and
friends all funnel through here. Meaningful at any phase: before
realize these are still queued (nothing wired to Tcl yet), after
realize they're the bindings actually in effect - covers both a
binding declared in the original build block and one added later
(e.g. from inside Session#add), so this stays a true live
picture, not just a record of what was queued pre-realize. Each
entry's own handler is the real Proc that runs, so
.source_location answers "what code does this" directly.
423 424 425 |
# File 'lib/teek/ui/handle.rb', line 423 def events @node.events end |
#hide ⇒ self
Hide the window: releases any grab #show set (a no-op if it
wasn't modal - Window#grab_release is always safe to call)
and withdraws it. Only valid on a ui.window handle.
326 327 328 329 330 331 332 333 334 |
# File 'lib/teek/ui/handle.rb', line 326 def hide unless type == :window raise ArgumentError, "hide only makes sense on a window (got a :#{type})" end grab_release window.withdraw self end |
#line(*coords, **opts) ⇒ CanvasItem
A straight line through the given points - [x1, y1, x2, y2, ...],
flat or nested, two or more points. Only valid on a ui.canvas handle.
343 344 345 |
# File 'lib/teek/ui/handle.rb', line 343 def line(*coords, **opts) create_canvas_item(:line, coords, opts) end |
#modal(global: false) { ... } ⇒ void
This method returns an undefined value.
Show the window modally: grabs input and sets focus on it
immediately. Release it explicitly with #grab_release (typically
from the window's own dismiss/close handling) when the dialog is
done - not released automatically just because this method
returns, since a modal dialog stays grabbed for its whole visible
lifetime, not just its setup. Released immediately if the optional
setup block itself raises, or if the window is destroyed while
still grabbed - see Window#modal, which this delegates to
entirely (no grab/focus/destroy-safety-net logic lives here).
Only valid on a ui.window handle.
276 277 278 279 280 281 282 |
# File 'lib/teek/ui/handle.rb', line 276 def modal(global: false, &block) unless type == :window raise ArgumentError, "modal only makes sense on a window (got a :#{type})" end window.modal(global: global, &block) end |
#name ⇒ Symbol?
Returns the node's explicit name.
38 39 40 |
# File 'lib/teek/ui/handle.rb', line 38 def name @node.name end |
#on_click { ... } ⇒ self
Fires on a left click.
157 158 159 160 |
# File 'lib/teek/ui/handle.rb', line 157 def on_click(&block) bind_event('<Button-1>', block) self end |
#on_close { ... } ⇒ self
Fires when the window's close button (titlebar close box, Cmd-W,
Alt-F4, ...) is pressed. Teek's own default (destroy the window)
only applies when nothing else has claimed it - the block decides
whether the window actually closes; call .destroy yourself if you
want that. Only valid on a ui.window handle.
225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/teek/ui/handle.rb', line 225 def on_close(&block) unless type == :window raise ArgumentError, "on_close only makes sense on a window (got a :#{type})" end if @node.realized @node.realized.app.on_close(window: @node.realized.path, &block) else @node.opts[:on_close] = block end self end |
#on_drag {|x, y| ... } ⇒ self
Fires while dragging (left button held down and moving). Delivers Integer x/y, converted through the widget's own canvasx/canvasy when bound to a canvas so callers never have to remember to do that themselves.
196 197 198 199 200 201 202 203 |
# File 'lib/teek/ui/handle.rb', line 196 def on_drag(&block) drag_type = type wrapped = lambda do |raw_x, raw_y| block.call(*convert_drag_coords(drag_type, raw_x, raw_y)) end bind_event('<B1-Motion>', wrapped, subs: %i[x y]) self end |
#on_key(spec) { ... } ⇒ self
Fires on a key press. spec is either a friendly Symbol (+:enter+,
:escape, :up, ...) or a "Modifier-Modifier-Key" String
(+"Ctrl-s"+, "Ctrl-Shift-s") - see Keysyms.
211 212 213 214 215 |
# File 'lib/teek/ui/handle.rb', line 211 def on_key(spec, &block) modifiers, keysym = Keysyms.resolve(spec) Keysyms.patterns_for(modifiers, keysym).each { |event| bind_event(event, block) } self end |
#on_right_click(menu = nil) { ... } ⇒ self
Fires on a right click, however the platform spells it (Button-3 on
Linux/Windows, Button-2 or Control-Button-1 on macOS). Either handle
it yourself with a block, or hand it a :menu/:context_menu
handle to pop up at the click's screen position - not both.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/teek/ui/handle.rb', line 170 def on_right_click( = nil, &block) if && block raise ArgumentError, "on_right_click takes either a menu handle or a block, not both" elsif unless MENU_HANDLE_TYPES.include?(.type) raise ArgumentError, "on_right_click(menu) needs a :menu or :context_menu handle (got a :#{.type})" end popup = lambda do |root_x, root_y| realized.app.(.path, x: root_x, y: root_y) end RIGHT_CLICK_EVENTS.each { |event| bind_event(event, popup, subs: %i[root_x root_y]) } elsif block RIGHT_CLICK_EVENTS.each { |event| bind_event(event, block) } else raise ArgumentError, "on_right_click needs either a menu handle or a block" end self end |
#on_tab_changed {|name_or_index| ... } ⇒ self
Fires when the selected tab changes (Tk's <t.tab(label, name)) if it has one, otherwise its plain
zero-based index - preferring a name over a raw Tk index, same as
ui[:name] lookup does everywhere else in the DSL. Only valid on a
ui.tabs handle.
247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/teek/ui/handle.rb', line 247 def on_tab_changed(&block) unless type == :tabs raise ArgumentError, "on_tab_changed only makes sense on a tabs container (got a :#{type})" end wrapped = lambda { index = realized.app.command(realized.path, :index, :current).to_i tab_node = @node.children[index] block.call(tab_node&.name || index) } bind_event('<<NotebookTabChanged>>', wrapped) self end |
#options ⇒ Hash{Symbol => String}
What Tk currently thinks this widget's (or, for a type with none
of its own, e.g. a menu entry - the entry's own) options are right
now, straight from a bare configure - for when a prior
#configure call seems like it silently didn't take, or just
exploring what's actually set on a live widget. Delegated to this
node type's WidgetType#addressing strategy, same as #configure.
76 77 78 |
# File 'lib/teek/ui/handle.rb', line 76 def @addressing.option_dump end |
#path ⇒ String
Returns this node's live address - the real Tk widget path for an ordinary widget, or (for a type with none of its own, e.g. a menu entry) its WidgetType#addressing strategy's own marked, Tk-path-shaped virtual path - see MenuEntryAddressing#virtual_path.
48 49 50 |
# File 'lib/teek/ui/handle.rb', line 48 def path @addressing.virtual_path end |
#polygon(*coords, **opts) ⇒ CanvasItem
A closed shape through the given points - [x1, y1, x2, y2, ...],
flat or nested, three or more points. Only valid on a ui.canvas handle.
366 367 368 |
# File 'lib/teek/ui/handle.rb', line 366 def polygon(*coords, **opts) create_canvas_item(:polygon, coords, opts) end |
#rectangle(*coords, **opts) ⇒ CanvasItem
A rectangle with corners [x1, y1, x2, y2]. Only valid on a
ui.canvas handle.
377 378 379 |
# File 'lib/teek/ui/handle.rb', line 377 def rectangle(*coords, **opts) create_canvas_item(:rectangle, coords, opts) end |
#release_focus ⇒ void Also known as: grab_release
This method returns an undefined value.
Release a grab previously set with #modal - "grab" is X11/Tk
jargon for capturing all input to one window, which is what a
modal dialog is doing while it's up. Only valid on a ui.window
handle. See Window#grab_release.
291 292 293 294 295 296 297 |
# File 'lib/teek/ui/handle.rb', line 291 def release_focus unless type == :window raise ArgumentError, "release_focus only makes sense on a window (got a :#{type})" end window.grab_release end |
#show ⇒ self
Reveal the window: positions it just to the right of the parent
it's nested under (root, or another window if this one's nested
inside it), deiconifies, raises it to the front, and - only if
this window was declared modal: true - grabs input and focuses
it too (via #modal). Only valid on a ui.window handle.
308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/teek/ui/handle.rb', line 308 def show unless type == :window raise ArgumentError, "show only makes sense on a window (got a :#{type})" end position_near_parent window.deiconify realized.app.command(:raise, realized.path) modal if @node.opts[:modal] self end |
#tagged(tag) ⇒ CanvasItem
A handle onto whatever items currently carry tag - zero, one, or
many (see CanvasItem, which addresses a tag and an id
identically). Doesn't create anything; a shape-creation method
(e.g. #line) already returns a single-item handle for its own
new item, this is for addressing a shared tags: group (or
reaching an item by an id you already have) after the fact. Only
valid on a ui.canvas handle.
438 439 440 441 |
# File 'lib/teek/ui/handle.rb', line 438 def tagged(tag) raise_unless_canvas!('tagged') CanvasItem.new(realized.app, realized.path, tag) end |
#text(*coords, **opts) ⇒ CanvasItem
Text anchored at [x, y]. Only valid on a ui.canvas handle.
387 388 389 |
# File 'lib/teek/ui/handle.rb', line 387 def text(*coords, **opts) create_canvas_item(:text, coords, opts) end |
#text_content ⇒ TextContent
The rich text API for this widget's content - insert/get/delete,
named formats (Tk's own "tag" concept), markers, search, and
embedded images. See TextContent for the full surface. Only
valid on a ui.text_area handle.
450 451 452 453 |
# File 'lib/teek/ui/handle.rb', line 450 def text_content raise_unless_text_area!('text_content') TextContent.new(realized.app, realized.path) end |
#type ⇒ Symbol
Returns the node's type, e.g. :button.
33 34 35 |
# File 'lib/teek/ui/handle.rb', line 33 def type @node.type end |