//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 12 Jun 08 Brian Frank Creation
//
**************************************************************************
** Point
**************************************************************************
**
** Point represents a coordinate in the display space.
**
@Js
@Serializable { simple = true }
const class Point
{
** Default instance is 0, 0.
const static Point defVal := Point(0, 0)
** Construct with x, y.
new make(Int x, Int y) { this.x = x; this.y = y }
** Parse from string. If invalid and checked is
** true then throw ParseErr otherwise return null.
static new fromStr(Str s, Bool checked := true)
{
try
{
comma := s.index(",")
return make(s[0..<comma].trim.toInt, s[comma+1..-1].trim.toInt)
}
catch {}
if (checked) throw ParseErr("Invalid Point: $s")
return null
}
** Return 'x+tx, y+ty'
Point translate(Point t) { make(x+t.x, y+t.y) }
** Return hash of x and y.
override Int hash() { x.xor(y.shiftl(16)) }
** Return if obj is same Point value.
override Bool equals(Obj? obj)
{
that := obj as Point
if (that == null) return false
return this.x == that.x && this.y == that.y
}
** Return '"x,y"'
override Str toStr() { "$x,$y" }
** X coordinate
const Int x
** Y coordinate
const Int y
}
**************************************************************************
** Size
**************************************************************************
**
** Size represents the width and height of a rectangle.
**
@Js
@Serializable { simple = true }
const class Size
{
** Default instance is 0, 0.
const static Size defVal := Size(0, 0)
** Construct with w, h.
new make(Int w, Int h) { this.w = w; this.h = h }
** Parse from string. If invalid and checked is
** true then throw ParseErr otherwise return null.
static new fromStr(Str s, Bool checked := true)
{
try
{
comma := s.index(",")
return make(s[0..<comma].trim.toInt, s[comma+1..-1].trim.toInt)
}
catch {}
if (checked) throw ParseErr("Invalid Size: $s")
return null
}
** Return '"w,h"'
override Str toStr() { "$w,$h" }
** Return hash of w and h.
override Int hash() { w.xor(h.shiftl(16)) }
** Return if obj is same Size value.
override Bool equals(Obj? obj)
{
that := obj as Size
if (that == null) return false
return this.w == that.w && this.h == that.h
}
** Width
const Int w
** Height
const Int h
}
**************************************************************************
** Rect
**************************************************************************
**
** Represents the x,y coordinate and w,h size of a rectangle.
**
@Js
@Serializable { simple = true }
const class Rect
{
** Default instance is 0, 0, 0, 0.
const static Rect defVal := Rect(0, 0, 0, 0)
** Construct with x, y, w, h.
new make(Int x, Int y, Int w, Int h)
{ this.x = x; this.y = y; this.w = w; this.h = h }
** Construct from a Point and Size instance
new makePosSize(Point p, Size s)
{ this.x = p.x; this.y = p.y; this.w = s.w; this.h= s.h }
** Parse from string. If invalid and checked is
** true then throw ParseErr otherwise return null.
static new fromStr(Str s, Bool checked := true)
{
try
{
c1 := s.index(",")
c2 := s.index(",", c1+1)
c3 := s.index(",", c2+1)
return make(s[0..<c1].trim.toInt, s[c1+1..<c2].trim.toInt,
s[c2+1..<c3].trim.toInt, s[c3+1..-1].trim.toInt)
}
catch {}
if (checked) throw ParseErr("Invalid Rect: $s")
return null
}
** Get the x, y coordinate of this rectangle.
Point pos() { Point(x, y) }
** Get the w, h size of this rectangle.
Size size() { Size(w, h) }
** Return true if x,y is inside the bounds of this rectangle.
Bool contains(Int x, Int y)
{
return x >= this.x && x <= this.x+w &&
y >= this.y && y <= this.y+h
}
** Return true if this rectangle intersects any portion of that rectangle
Bool intersects(Rect that)
{
ax1 := this.x; ay1 := this.y; ax2 := ax1 + this.w; ay2 := ay1 + this.h
bx1 := that.x; by1 := that.y; bx2 := bx1 + that.w; by2 := by1 + that.h
return !(ax2 <= bx1 || bx2 <= ax1 || ay2 <= by1 || by2 <= ay1)
}
** Compute the intersection between this rectangle and that rectangle.
** If there is no intersection, then return `defVal`.
Rect intersection(Rect that)
{
ax1 := this.x; ay1 := this.y; ax2 := ax1 + this.w; ay2 := ay1 + this.h
bx1 := that.x; by1 := that.y; bx2 := bx1 + that.w; by2 := by1 + that.h
rx1 := ax1.max(bx1); rx2 := ax2.min(bx2)
ry1 := ay1.max(by1); ry2 := ay2.min(by2)
rw := rx2 - rx1
rh := ry2 - ry1
if (rw <= 0 || rh <= 0) return defVal
return make(rx1, ry1, rw, rh)
}
** Compute the union between this rectangle and that rectangle,
** which is the bounding box that exactly contains both rectangles.
Rect union(Rect that)
{
ax1 := this.x; ay1 := this.y; ax2 := ax1 + this.w; ay2 := ay1 + this.h
bx1 := that.x; by1 := that.y; bx2 := bx1 + that.w; by2 := by1 + that.h
rx1 := ax1.min(bx1); rx2 := ax2.max(bx2)
ry1 := ay1.min(by1); ry2 := ay2.max(by2)
rw := rx2 - rx1
rh := ry2 - ry1
if (rw <= 0 || rh <= 0) return defVal
return make(rx1, ry1, rw, rh)
}
** Return '"x,y,w,h"'
override Str toStr() { return "$x,$y,$w,$h" }
** Return hash of x, y, w, and h.
override Int hash()
{
x.xor(y.shiftl(8)).xor(w.shiftl(16)).xor(w.shiftl(24))
}
** Return if obj is same Rect value.
override Bool equals(Obj? obj)
{
that := obj as Rect
if (that == null) return false
return this.x == that.x && this.y == that.y &&
this.w == that.w && this.h == that.h
}
** X coordinate
const Int x
** Y coordinate
const Int y
** Width
const Int w
** Height
const Int h
}
**************************************************************************
** Insets
**************************************************************************
**
** Insets represent a number of pixels around the edge of a rectangle.
**
@Js
@Serializable { simple = true }
const class Insets
{
** Default instance 0, 0, 0, 0.
const static Insets defVal := Insets(0, 0, 0, 0)
**
** Construct with top, and optional right, bottom, left. If one side
** is not specified, it is reflected from the opposite side:
**
** Insets(5) => Insets(5,5,5,5)
** Insets(5,6) => Insets(5,6,5,6)
** Insets(5,6,7) => Insets(5,6,7,6)
**
new make(Int top, Int? right := null, Int? bottom := null, Int? left := null)
{
if (right == null) right = top
if (bottom == null) bottom = top
if (left == null) left = right
this.top = top
this.right = right
this.bottom = bottom
this.left = left
}
** Parse from string (see `toStr`). If invalid and checked
** is true then throw ParseErr otherwise return null. Supported
** formats are:
** - "len"
** - "top,right,bottom,left"
static new fromStr(Str s, Bool checked := true)
{
try
{
c1 := s.index(",")
if (c1 == null) { len := s.toInt; return make(len, len, len, len) }
c2 := s.index(",", c1+1)
c3 := s.index(",", c2+1)
return make(s[0..<c1].trim.toInt, s[c1+1..<c2].trim.toInt,
s[c2+1..<c3].trim.toInt, s[c3+1..-1].trim.toInt)
}
catch {}
if (checked) throw ParseErr("Invalid Insets: $s")
return null
}
** If all four sides are equal return '"len"'
** otherwise return '"top,right,bottom,left"'.
override Str toStr()
{
if (top == right && top == bottom && top == left)
return top.toStr
else
return "$top,$right,$bottom,$left"
}
** Return hash of top, right, bottom, left.
override Int hash()
{
top.xor(right.shiftl(8)).xor(bottom.shiftl(16)).xor(left.shiftl(24))
}
** Return if obj is same Insets value.
override Bool equals(Obj? obj)
{
that := obj as Insets
if (that == null) return false
return this.top == that.top && this.right == that.right &&
this.bottom == that.bottom && this.left == that.left
}
** Return right+left, top+bottom
Size toSize() { Size(right+left, top+bottom) }
** Top side spacing
const Int top
** Right side spacing
const Int right
** Bottom side spacing
const Int bottom
** Left side spacing
const Int left
}
**************************************************************************
** Hints
**************************************************************************
**
** Hints model heights/weight contraints. Hint differs from Size
** in that 'w' or 'h' can be null.
**
@Js
const class Hints
{
** Default instance is null, null.
const static Hints defVal := Hints(null, null)
** Construct with w, h.
new make(Int? w, Int? h) { this.w = w; this.h = h }
** Return '"w,h"'
override Str toStr() { "$w,$h" }
** Return hash of w and h.
override Int hash()
{
(w == null ? 3 : w.hash).xor((h == null ? 11 : h.hash).shiftl(16))
}
** Return if obj is same Hints value.
override Bool equals(Obj? obj)
{
that := obj as Hints
if (that == null) return false
return this.w == that.w && this.h == that.h
}
** Add the given w and h to this hint's dimensions. If a hint
** dimension is null, then the resulting dimension is null too.
@Operator Hints plus(Size size)
{
make(w == null ? null : w + size.w, h == null ? null : h + size.h)
}
** Subtract the given w and h from this hint's dimensions. If a hint
** dimension is null, then the resulting dimension is null too.
@Operator Hints minus(Size size)
{
make(w == null ? null : w - size.w, h == null ? null : h - size.h)
}
** Suggested width or null if no contraints
const Int? w
** Suggested height or null if no contraints
const Int? h
}