Class: Tuile::Rect

Inherits:
Object
  • Object
show all
Defined in:
lib/tuile/rect.rb,
sig/tuile.rbs

Overview

A rectangle, with integer left, top, width and height, all 0-based.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#height ⇒ Integer (readonly)

@return — height.

Returns:

  • (Integer)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/tuile/rect.rb', line 14

class Rect < Data.define(:left, :top, :width, :height)
  # @return [String]
  def to_s = "#{top_left} #{size}"

  # @return [Boolean] true if either {#width} or {#height} is zero or negative.
  def empty?
    width <= 0 || height <= 0
  end

  # @param point [Point] new top-left corner.
  # @return [Rect] positioned at the new `left`/`top`.
  def at(point)
    Rect.new(point.x, point.y, width, height)
  end

  # {#at}'s relative counterpart — the same size, shifted. What moves a
  # rectangle between two coordinate spaces one offset apart, either way;
  # paint and screen, a {Canvas#origin} apart, are the pair Tuile has.
  # @param point [Point] added to {#left} and {#top}.
  # @return [Rect] moved by `point`.
  def moved_by(point)
    Rect.new(left + point.x, top + point.y, width, height)
  end

  # Centers the rectangle — keeps {#width} and {#height} but modifies
  # {#top} and {#left} so that the rectangle is centered on a screen.
  # @param screen_size [Size] screen size
  # @return [Rect] moved rectangle.
  def centered(screen_size)
    at(Point.new((screen_size.width - width) / 2, (screen_size.height - height) / 2))
  end

  # Clamp both width and height and return a rectangle.
  # @param max_size [Size] the max size
  # @return [Rect]
  def clamp(max_size)
    new_width = width.clamp(nil, max_size.width)
    new_height = height.clamp(nil, max_size.height)
    new_width == width && new_height == height ? self : Rect.new(left, top, new_width, new_height)
  end

  # Half-open: the `left`/`top` edges are inside, `right`/`bottom` are not,
  # so two abutting rectangles never both claim the cell they share.
  #
  #   r = Rect.new(0, 0, 2, 2)
  #   r.contains?(Point.new(0, 0))   # => true
  #   r.contains?(Point.new(2, 0))   # => false — right edge is outside
  #
  # @param point [Point]
  # @return [Boolean]
  def contains?(point)
    point.x >= left && point.x < left + width && point.y >= top && point.y < top + height
  end

  # @param other [Rect] another rectangle.
  # @return [Boolean] true if `other` lies entirely within this rectangle.
  #   Uses the same half-open edges as {#contains?} (right/bottom exclusive).
  #   An {#empty? empty} `other` covers no cells, so it is trivially contained.
  def contains_rect?(other)
    return true if other.empty?

    other.left >= left && other.top >= top &&
      other.left + other.width <= left + width &&
      other.top + other.height <= top + height
  end

  # The region both rectangles cover, in the coordinate space they share.
  # Half-open edges, like {#contains?}.
  #
  # Disjoint rectangles yield an {#empty? empty} rectangle rather than `nil`,
  # so folding a chain of them needs no nil test per level and the caller
  # asks {#empty?} once at the end — which is what a clip resolved up an
  # ancestor chain does.
  # @param other [Rect]
  # @return [Rect]
  def intersect(other)
    new_left = [left, other.left].max
    new_top = [top, other.top].max
    Rect.new(new_left, new_top,
             [[left + width, other.left + other.width].min - new_left, 0].max,
             [[top + height, other.top + other.height].min - new_top, 0].max)
  end

  # @return [Size]
  def size = Size.new(width, height)

  # @return [Point]
  def top_left = Point.new(left, top)
end

#left ⇒ Integer (readonly)

@return — left edge, 0-based.

Returns:

  • (Integer)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/tuile/rect.rb', line 14

class Rect < Data.define(:left, :top, :width, :height)
  # @return [String]
  def to_s = "#{top_left} #{size}"

  # @return [Boolean] true if either {#width} or {#height} is zero or negative.
  def empty?
    width <= 0 || height <= 0
  end

  # @param point [Point] new top-left corner.
  # @return [Rect] positioned at the new `left`/`top`.
  def at(point)
    Rect.new(point.x, point.y, width, height)
  end

  # {#at}'s relative counterpart — the same size, shifted. What moves a
  # rectangle between two coordinate spaces one offset apart, either way;
  # paint and screen, a {Canvas#origin} apart, are the pair Tuile has.
  # @param point [Point] added to {#left} and {#top}.
  # @return [Rect] moved by `point`.
  def moved_by(point)
    Rect.new(left + point.x, top + point.y, width, height)
  end

  # Centers the rectangle — keeps {#width} and {#height} but modifies
  # {#top} and {#left} so that the rectangle is centered on a screen.
  # @param screen_size [Size] screen size
  # @return [Rect] moved rectangle.
  def centered(screen_size)
    at(Point.new((screen_size.width - width) / 2, (screen_size.height - height) / 2))
  end

  # Clamp both width and height and return a rectangle.
  # @param max_size [Size] the max size
  # @return [Rect]
  def clamp(max_size)
    new_width = width.clamp(nil, max_size.width)
    new_height = height.clamp(nil, max_size.height)
    new_width == width && new_height == height ? self : Rect.new(left, top, new_width, new_height)
  end

  # Half-open: the `left`/`top` edges are inside, `right`/`bottom` are not,
  # so two abutting rectangles never both claim the cell they share.
  #
  #   r = Rect.new(0, 0, 2, 2)
  #   r.contains?(Point.new(0, 0))   # => true
  #   r.contains?(Point.new(2, 0))   # => false — right edge is outside
  #
  # @param point [Point]
  # @return [Boolean]
  def contains?(point)
    point.x >= left && point.x < left + width && point.y >= top && point.y < top + height
  end

  # @param other [Rect] another rectangle.
  # @return [Boolean] true if `other` lies entirely within this rectangle.
  #   Uses the same half-open edges as {#contains?} (right/bottom exclusive).
  #   An {#empty? empty} `other` covers no cells, so it is trivially contained.
  def contains_rect?(other)
    return true if other.empty?

    other.left >= left && other.top >= top &&
      other.left + other.width <= left + width &&
      other.top + other.height <= top + height
  end

  # The region both rectangles cover, in the coordinate space they share.
  # Half-open edges, like {#contains?}.
  #
  # Disjoint rectangles yield an {#empty? empty} rectangle rather than `nil`,
  # so folding a chain of them needs no nil test per level and the caller
  # asks {#empty?} once at the end — which is what a clip resolved up an
  # ancestor chain does.
  # @param other [Rect]
  # @return [Rect]
  def intersect(other)
    new_left = [left, other.left].max
    new_top = [top, other.top].max
    Rect.new(new_left, new_top,
             [[left + width, other.left + other.width].min - new_left, 0].max,
             [[top + height, other.top + other.height].min - new_top, 0].max)
  end

  # @return [Size]
  def size = Size.new(width, height)

  # @return [Point]
  def top_left = Point.new(left, top)
end

#top ⇒ Integer (readonly)

@return — top edge, 0-based.

Returns:

  • (Integer)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/tuile/rect.rb', line 14

class Rect < Data.define(:left, :top, :width, :height)
  # @return [String]
  def to_s = "#{top_left} #{size}"

  # @return [Boolean] true if either {#width} or {#height} is zero or negative.
  def empty?
    width <= 0 || height <= 0
  end

  # @param point [Point] new top-left corner.
  # @return [Rect] positioned at the new `left`/`top`.
  def at(point)
    Rect.new(point.x, point.y, width, height)
  end

  # {#at}'s relative counterpart — the same size, shifted. What moves a
  # rectangle between two coordinate spaces one offset apart, either way;
  # paint and screen, a {Canvas#origin} apart, are the pair Tuile has.
  # @param point [Point] added to {#left} and {#top}.
  # @return [Rect] moved by `point`.
  def moved_by(point)
    Rect.new(left + point.x, top + point.y, width, height)
  end

  # Centers the rectangle — keeps {#width} and {#height} but modifies
  # {#top} and {#left} so that the rectangle is centered on a screen.
  # @param screen_size [Size] screen size
  # @return [Rect] moved rectangle.
  def centered(screen_size)
    at(Point.new((screen_size.width - width) / 2, (screen_size.height - height) / 2))
  end

  # Clamp both width and height and return a rectangle.
  # @param max_size [Size] the max size
  # @return [Rect]
  def clamp(max_size)
    new_width = width.clamp(nil, max_size.width)
    new_height = height.clamp(nil, max_size.height)
    new_width == width && new_height == height ? self : Rect.new(left, top, new_width, new_height)
  end

  # Half-open: the `left`/`top` edges are inside, `right`/`bottom` are not,
  # so two abutting rectangles never both claim the cell they share.
  #
  #   r = Rect.new(0, 0, 2, 2)
  #   r.contains?(Point.new(0, 0))   # => true
  #   r.contains?(Point.new(2, 0))   # => false — right edge is outside
  #
  # @param point [Point]
  # @return [Boolean]
  def contains?(point)
    point.x >= left && point.x < left + width && point.y >= top && point.y < top + height
  end

  # @param other [Rect] another rectangle.
  # @return [Boolean] true if `other` lies entirely within this rectangle.
  #   Uses the same half-open edges as {#contains?} (right/bottom exclusive).
  #   An {#empty? empty} `other` covers no cells, so it is trivially contained.
  def contains_rect?(other)
    return true if other.empty?

    other.left >= left && other.top >= top &&
      other.left + other.width <= left + width &&
      other.top + other.height <= top + height
  end

  # The region both rectangles cover, in the coordinate space they share.
  # Half-open edges, like {#contains?}.
  #
  # Disjoint rectangles yield an {#empty? empty} rectangle rather than `nil`,
  # so folding a chain of them needs no nil test per level and the caller
  # asks {#empty?} once at the end — which is what a clip resolved up an
  # ancestor chain does.
  # @param other [Rect]
  # @return [Rect]
  def intersect(other)
    new_left = [left, other.left].max
    new_top = [top, other.top].max
    Rect.new(new_left, new_top,
             [[left + width, other.left + other.width].min - new_left, 0].max,
             [[top + height, other.top + other.height].min - new_top, 0].max)
  end

  # @return [Size]
  def size = Size.new(width, height)

  # @return [Point]
  def top_left = Point.new(left, top)
end

#width ⇒ Integer (readonly)

@return — width.

Returns:

  • (Integer)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/tuile/rect.rb', line 14

class Rect < Data.define(:left, :top, :width, :height)
  # @return [String]
  def to_s = "#{top_left} #{size}"

  # @return [Boolean] true if either {#width} or {#height} is zero or negative.
  def empty?
    width <= 0 || height <= 0
  end

  # @param point [Point] new top-left corner.
  # @return [Rect] positioned at the new `left`/`top`.
  def at(point)
    Rect.new(point.x, point.y, width, height)
  end

  # {#at}'s relative counterpart — the same size, shifted. What moves a
  # rectangle between two coordinate spaces one offset apart, either way;
  # paint and screen, a {Canvas#origin} apart, are the pair Tuile has.
  # @param point [Point] added to {#left} and {#top}.
  # @return [Rect] moved by `point`.
  def moved_by(point)
    Rect.new(left + point.x, top + point.y, width, height)
  end

  # Centers the rectangle — keeps {#width} and {#height} but modifies
  # {#top} and {#left} so that the rectangle is centered on a screen.
  # @param screen_size [Size] screen size
  # @return [Rect] moved rectangle.
  def centered(screen_size)
    at(Point.new((screen_size.width - width) / 2, (screen_size.height - height) / 2))
  end

  # Clamp both width and height and return a rectangle.
  # @param max_size [Size] the max size
  # @return [Rect]
  def clamp(max_size)
    new_width = width.clamp(nil, max_size.width)
    new_height = height.clamp(nil, max_size.height)
    new_width == width && new_height == height ? self : Rect.new(left, top, new_width, new_height)
  end

  # Half-open: the `left`/`top` edges are inside, `right`/`bottom` are not,
  # so two abutting rectangles never both claim the cell they share.
  #
  #   r = Rect.new(0, 0, 2, 2)
  #   r.contains?(Point.new(0, 0))   # => true
  #   r.contains?(Point.new(2, 0))   # => false — right edge is outside
  #
  # @param point [Point]
  # @return [Boolean]
  def contains?(point)
    point.x >= left && point.x < left + width && point.y >= top && point.y < top + height
  end

  # @param other [Rect] another rectangle.
  # @return [Boolean] true if `other` lies entirely within this rectangle.
  #   Uses the same half-open edges as {#contains?} (right/bottom exclusive).
  #   An {#empty? empty} `other` covers no cells, so it is trivially contained.
  def contains_rect?(other)
    return true if other.empty?

    other.left >= left && other.top >= top &&
      other.left + other.width <= left + width &&
      other.top + other.height <= top + height
  end

  # The region both rectangles cover, in the coordinate space they share.
  # Half-open edges, like {#contains?}.
  #
  # Disjoint rectangles yield an {#empty? empty} rectangle rather than `nil`,
  # so folding a chain of them needs no nil test per level and the caller
  # asks {#empty?} once at the end — which is what a clip resolved up an
  # ancestor chain does.
  # @param other [Rect]
  # @return [Rect]
  def intersect(other)
    new_left = [left, other.left].max
    new_top = [top, other.top].max
    Rect.new(new_left, new_top,
             [[left + width, other.left + other.width].min - new_left, 0].max,
             [[top + height, other.top + other.height].min - new_top, 0].max)
  end

  # @return [Size]
  def size = Size.new(width, height)

  # @return [Point]
  def top_left = Point.new(left, top)
end

Instance Method Details

#at(point) ⇒ Rect

@param point — new top-left corner.

@return — positioned at the new left/top.

Parameters:

Returns:



25
26
27
# File 'lib/tuile/rect.rb', line 25

def at(point)
  Rect.new(point.x, point.y, width, height)
end

#centered(screen_size) ⇒ Rect

Centers the rectangle — keeps #width and #height but modifies #top and #left so that the rectangle is centered on a screen.

@param screen_size — screen size

@return — moved rectangle.

Parameters:

  • screen_size (Size)

Returns:



42
43
44
# File 'lib/tuile/rect.rb', line 42

def centered(screen_size)
  at(Point.new((screen_size.width - width) / 2, (screen_size.height - height) / 2))
end

#clamp(max_size) ⇒ Rect

Clamp both width and height and return a rectangle.

@param max_size — the max size

Parameters:

Returns:



49
50
51
52
53
# File 'lib/tuile/rect.rb', line 49

def clamp(max_size)
  new_width = width.clamp(nil, max_size.width)
  new_height = height.clamp(nil, max_size.height)
  new_width == width && new_height == height ? self : Rect.new(left, top, new_width, new_height)
end

#contains?(point) ⇒ Boolean

Half-open: the left/top edges are inside, right/bottom are not, so two abutting rectangles never both claim the cell they share.

r = Rect.new(0, 0, 2, 2)
r.contains?(Point.new(0, 0))   # => true
r.contains?(Point.new(2, 0))   # => false — right edge is outside

@param point

Parameters:

Returns:

  • (Boolean)


64
65
66
# File 'lib/tuile/rect.rb', line 64

def contains?(point)
  point.x >= left && point.x < left + width && point.y >= top && point.y < top + height
end

#contains_rect?(other) ⇒ Boolean

@param other — another rectangle.

@return — true if other lies entirely within this rectangle. Uses the same half-open edges as #contains? (right/bottom exclusive). An empty other covers no cells, so it is trivially contained.

Parameters:

Returns:

  • (Boolean)


72
73
74
75
76
77
78
# File 'lib/tuile/rect.rb', line 72

def contains_rect?(other)
  return true if other.empty?

  other.left >= left && other.top >= top &&
    other.left + other.width <= left + width &&
    other.top + other.height <= top + height
end

#empty? ⇒ Boolean

@return — true if either #width or #height is zero or negative.

Returns:

  • (Boolean)


19
20
21
# File 'lib/tuile/rect.rb', line 19

def empty?
  width <= 0 || height <= 0
end

#intersect(other) ⇒ Rect

The region both rectangles cover, in the coordinate space they share. Half-open edges, like #contains?.

Disjoint rectangles yield an empty rectangle rather than nil, so folding a chain of them needs no nil test per level and the caller asks #empty? once at the end — which is what a clip resolved up an ancestor chain does.

@param other

Parameters:

Returns:



89
90
91
92
93
94
95
# File 'lib/tuile/rect.rb', line 89

def intersect(other)
  new_left = [left, other.left].max
  new_top = [top, other.top].max
  Rect.new(new_left, new_top,
           [[left + width, other.left + other.width].min - new_left, 0].max,
           [[top + height, other.top + other.height].min - new_top, 0].max)
end

#moved_by(point) ⇒ Rect

#at's relative counterpart — the same size, shifted. What moves a rectangle between two coordinate spaces one offset apart, either way; paint and screen, a Canvas#origin apart, are the pair Tuile has.

@param point — added to #left and #top.

@return — moved by point.

Parameters:

Returns:



34
35
36
# File 'lib/tuile/rect.rb', line 34

def moved_by(point)
  Rect.new(left + point.x, top + point.y, width, height)
end

#size ⇒ Size

Returns:



98
# File 'lib/tuile/rect.rb', line 98

def size = Size.new(width, height)

#to_s ⇒ String

Returns:

  • (String)


16
# File 'lib/tuile/rect.rb', line 16

def to_s = "#{top_left} #{size}"

#top_left ⇒ Point

Returns:



101
# File 'lib/tuile/rect.rb', line 101

def top_left = Point.new(left, top)