Class: Teek::UI::CanvasItem

Inherits:
Object
  • Object
show all
Defined in:
lib/teek/ui/canvas_item.rb

Overview

A handle onto one or more canvas items, addressed by Tk's own tagOrId - either the numeric id a create call returns (a single item) or an arbitrary tag string (every item currently carrying it, zero or more). Every method here is exactly that uniform - Tk's own canvas command already treats a tag and an id identically for move/coords/itemconfigure/delete/stacking/scale, so a shared tag is addressable as a group with no separate "group handle" type: get one via a shape-creation method (see Handle#line and friends, a single-item handle) or Handle#tagged (an existing tag, whatever it currently matches).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, canvas_path, tag_or_id) ⇒ CanvasItem

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 CanvasItem.



22
23
24
25
26
# File 'lib/teek/ui/canvas_item.rb', line 22

def initialize(app, canvas_path, tag_or_id)
  @app = app
  @canvas_path = canvas_path
  @tag_or_id = tag_or_id.to_s
end

Instance Attribute Details

#tag_or_idString (readonly)

Returns the tagOrId this handle addresses.

Returns:

  • (String)

    the tagOrId this handle addresses



19
20
21
# File 'lib/teek/ui/canvas_item.rb', line 19

def tag_or_id
  @tag_or_id
end

Instance Method Details

#[](opt) ⇒ String

Read back a single item option - item[:fill].

Parameters:

  • opt (Symbol, String)

    e.g. :fill

Returns:

  • (String)


77
78
79
# File 'lib/teek/ui/canvas_item.rb', line 77

def [](opt)
  @app.command(@canvas_path, :itemcget, @tag_or_id, "-#{opt}")
end

#[]=(opt, value) ⇒ Object

Set a single item option - item[:fill] = 'red'. Shorthand for configure(opt => value) when there's only one to change.

Parameters:

  • opt (Symbol, String)

    e.g. :fill

  • value (Object)

Returns:

  • (Object)

    value



86
87
88
89
# File 'lib/teek/ui/canvas_item.rb', line 86

def []=(opt, value)
  configure(opt => value)
  value
end

#boundsArray<Float>? Also known as: bbox

Returns [x1, y1, x2, y2] bounding box, or nil if nothing currently matches #tag_or_id.

Returns:

  • (Array<Float>, nil)

    [x1, y1, x2, y2] bounding box, or nil if nothing currently matches #tag_or_id



140
141
142
143
# File 'lib/teek/ui/canvas_item.rb', line 140

def bounds
  result = @app.command(@canvas_path, :bbox, @tag_or_id)
  result.empty? ? nil : @app.split_list(result).map(&:to_f)
end

#bring_to_front(above = nil) ⇒ self Also known as: tk_raise

Bring to the front of the stacking order (drawn last, on top of everything), or - given above - just in front of that one item/tag instead of all the way to the front.

Parameters:

  • above (CanvasItem, String, nil) (defaults to: nil)

Returns:

  • (self)


103
104
105
106
107
# File 'lib/teek/ui/canvas_item.rb', line 103

def bring_to_front(above = nil)
  args = above ? [resolve(above)] : []
  @app.command(@canvas_path, :raise, @tag_or_id, *args)
  self
end

#configure(**opts) ⇒ self

Mutate several item options at once.

Parameters:

  • opts (Hash)

    item options, e.g. fill: 'red'

Returns:

  • (self)


69
70
71
72
# File 'lib/teek/ui/canvas_item.rb', line 69

def configure(**opts)
  @app.command(@canvas_path, :itemconfigure, @tag_or_id, **opts)
  self
end

#deletenil

Remove the item(s) from the canvas.

Returns:

  • (nil)


93
94
95
96
# File 'lib/teek/ui/canvas_item.rb', line 93

def delete
  @app.command(@canvas_path, :delete, @tag_or_id)
  nil
end

#draggable {|x, y| ... } ⇒ self

Makes this item movable by mouse drag, with zero coordinate math of your own - press and drag it around the canvas, it follows the pointer. Binds <Button-1> (to capture the starting position) and <B1-Motion> (to shift #move by the delta each tick) on this item/tag, replacing any #on_click/#on_drag binding already set on it, the same way any two binds on the same item/event replace each other in Tk.

Yields:

  • (x, y)

    optional - the item's new pointer-relative position (same Integer canvasx/canvasy coordinates #on_drag delivers) after each move, e.g. to react to where it's been dragged to

Returns:

  • (self)


221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/teek/ui/canvas_item.rb', line 221

def draggable(&block)
  last = nil

  press = lambda do |raw_x, raw_y|
    last = canvas_xy(raw_x, raw_y)
  end
  bind_item_event('<Button-1>', press, subs: %i[x y])

  drag = lambda do |raw_x, raw_y|
    x, y = canvas_xy(raw_x, raw_y)
    move(x - last[0], y - last[1])
    last = [x, y]
    block.call(x, y) if block
  end
  bind_item_event('<B1-Motion>', drag, subs: %i[x y])

  self
end

#exists?Boolean

Returns whether any item currently matches #tag_or_id - always true for a single-item handle from a creation method (the item exists until #deleted), meaningful for a Handle#tagged group that may currently match zero items.

Returns:

  • (Boolean)

    whether any item currently matches #tag_or_id - always true for a single-item handle from a creation method (the item exists until #deleted), meaningful for a Handle#tagged group that may currently match zero items



150
151
152
# File 'lib/teek/ui/canvas_item.rb', line 150

def exists?
  !@app.command(@canvas_path, :find, :withtag, @tag_or_id).empty?
end

#move(dx, dy) ⇒ self

Move relative to the current position.

Parameters:

  • dx (Numeric)
  • dy (Numeric)

Returns:

  • (self)


44
45
46
47
# File 'lib/teek/ui/canvas_item.rb', line 44

def move(dx, dy)
  @app.command(@canvas_path, :move, @tag_or_id, dx, dy)
  self
end

#on_click { ... } ⇒ self

Fires on a left click, only when the click lands on this specific item/tag - other items on the same canvas are untouched. Wired immediately, via the canvas's own bind subcommand (Tk has no per-item widget path to bind a plain bind against) - unlike Handle, a CanvasItem only ever exists post-realize, so there's no queue-before-realize phase to worry about here.

Yields:

  • called with no arguments

Returns:

  • (self)


162
163
164
165
# File 'lib/teek/ui/canvas_item.rb', line 162

def on_click(&block)
  bind_item_event('<Button-1>', block)
  self
end

#on_drag {|x, y| ... } ⇒ self

Fires while dragging this item (left button held down and moving). Delivers Integer x/y already converted through the canvas's own canvasx/canvasy, same as Handle#on_drag does when bound to a canvas - callers never have to remember to do that themselves.

Yields:

  • (x, y)

    Integer coordinates

Returns:

  • (self)


201
202
203
204
205
206
207
208
# File 'lib/teek/ui/canvas_item.rb', line 201

def on_drag(&block)
  wrapped = lambda do |raw_x, raw_y|
    x, y = canvas_xy(raw_x, raw_y)
    block.call(x, y)
  end
  bind_item_event('<B1-Motion>', wrapped, subs: %i[x y])
  self
end

#on_right_click(menu = nil) { ... } ⇒ self

Fires on a right click, however the platform spells it - see MouseEvents::RIGHT_CLICK_EVENTS. 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.

Parameters:

  • menu (Handle, nil) (defaults to: nil)

    a :menu or :context_menu handle to tk_popup

Yields:

  • called with no arguments (only when menu isn't given)

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if given neither or both, or menu isn't a menu handle



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/teek/ui/canvas_item.rb', line 175

def on_right_click(menu = nil, &block)
  if menu && block
    raise ArgumentError, "on_right_click takes either a menu handle or a block, not both"
  elsif menu
    unless MouseEvents::MENU_HANDLE_TYPES.include?(menu.type)
      raise ArgumentError, "on_right_click(menu) needs a :menu or :context_menu handle (got a :#{menu.type})"
    end

    popup = lambda do |root_x, root_y|
      @app.popup_menu(menu.path, x: root_x, y: root_y)
    end
    MouseEvents::RIGHT_CLICK_EVENTS.each { |event| bind_item_event(event, popup, subs: %i[root_x root_y]) }
  elsif block
    MouseEvents::RIGHT_CLICK_EVENTS.each { |event| bind_item_event(event, block) }
  else
    raise ArgumentError, "on_right_click needs either a menu handle or a block"
  end
  self
end

#pointsArray<Float> Also known as: coords

Returns the current coordinate list.

Returns:

  • (Array<Float>)

    the current coordinate list



50
51
52
53
# File 'lib/teek/ui/canvas_item.rb', line 50

def points
  result = @app.command(@canvas_path, :coords, @tag_or_id)
  @app.split_list(result).map(&:to_f)
end

#points=(new_coords) ⇒ void Also known as: coords=

This method returns an undefined value.

Replace the coordinate list outright (as opposed to #move's relative shift).

Parameters:

  • new_coords (Array<Numeric>)

    flat or nested (e.g. [[x1, y1], [x2, y2]]) - flattened either way



61
62
63
# File 'lib/teek/ui/canvas_item.rb', line 61

def points=(new_coords)
  @app.command(@canvas_path, :coords, @tag_or_id, *new_coords.flatten)
end

#scale(ox, oy, sx, sy) ⇒ self

Scale coordinates relative to a fixed point.

Parameters:

  • ox (Numeric)

    x origin scaling is relative to

  • oy (Numeric)

    y origin scaling is relative to

  • sx (Numeric)

    x scale factor

  • sy (Numeric)

    y scale factor

Returns:

  • (self)


133
134
135
136
# File 'lib/teek/ui/canvas_item.rb', line 133

def scale(ox, oy, sx, sy)
  @app.command(@canvas_path, :scale, @tag_or_id, ox, oy, sx, sy)
  self
end

#send_to_back(below = nil) ⇒ self Also known as: lower

Send to the back of the stacking order (drawn first, under everything), or - given below - just behind that one item/tag instead of all the way to the back.

Parameters:

  • below (CanvasItem, String, nil) (defaults to: nil)

Returns:

  • (self)


120
121
122
123
124
# File 'lib/teek/ui/canvas_item.rb', line 120

def send_to_back(below = nil)
  args = below ? [resolve(below)] : []
  @app.command(@canvas_path, :lower, @tag_or_id, *args)
  self
end

#virtual_pathString

Returns the canvas's own path, marked past the point a real Tk path stops applying - an item/tag has no independent Tk path of its own, only the canvas does. ! is illegal in a Tk path segment, so handing this to a raw Tk command fails loudly (an "invalid command name" Tcl error) instead of silently misbehaving - the same marked-address shape MenuEntryAddressing#virtual_path uses for a menu entry, the other kind of thing with no Tk path of its own.

Returns:

  • (String)

    the canvas's own path, marked past the point a real Tk path stops applying - an item/tag has no independent Tk path of its own, only the canvas does. ! is illegal in a Tk path segment, so handing this to a raw Tk command fails loudly (an "invalid command name" Tcl error) instead of silently misbehaving - the same marked-address shape MenuEntryAddressing#virtual_path uses for a menu entry, the other kind of thing with no Tk path of its own.



36
37
38
# File 'lib/teek/ui/canvas_item.rb', line 36

def virtual_path
  "#{@canvas_path}!#{@tag_or_id}"
end