diff options
Diffstat (limited to 'src/Box.vala')
-rw-r--r-- | src/Box.vala | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/src/Box.vala b/src/Box.vala new file mode 100644 index 0000000..f48bcfb --- /dev/null +++ b/src/Box.vala @@ -0,0 +1,403 @@ +/* Copyright 2009-2014 Yorba Foundation + * + * This software is licensed under the GNU LGPL (version 2.1 or later). + * See the COPYING file in this distribution. + */ + +public enum BoxLocation { + OUTSIDE, + INSIDE, + TOP_SIDE, + LEFT_SIDE, + RIGHT_SIDE, + BOTTOM_SIDE, + TOP_LEFT, + BOTTOM_LEFT, + TOP_RIGHT, + BOTTOM_RIGHT +} + +public enum BoxComplements { + NONE, + VERTICAL, + HORIZONTAL, + BOTH; + + public static BoxComplements derive(bool horizontal_complement, bool vertical_complement) { + if (horizontal_complement && vertical_complement) + return BOTH; + else if(horizontal_complement) + return HORIZONTAL; + else if (vertical_complement) + return VERTICAL; + + return NONE; + } +} + +public struct Box { + public static const int HAND_GRENADES = 12; + + public int left; + public int top; + public int right; + public int bottom; + + public Box(int left = 0, int top = 0, int right = 0, int bottom = 0) { + // Sanity check on top left vertex. + left = left.clamp(0, int.MAX); + top = top.clamp(0, int.MAX); + + // Sanity check on dimensions - force + // box to be at least 1 px by 1 px. + if (right <= left) + right = left + 1; + + if (bottom <= top) + bottom = top + 1; + + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + public static Box from_rectangle(Gdk.Rectangle rect) { + return Box(rect.x, rect.y, rect.x + rect.width - 1, rect.y + rect.height - 1); + } + + public static Box from_allocation(Gtk.Allocation alloc) { + return Box(alloc.x, alloc.y, alloc.x + alloc.width - 1, alloc.y + alloc.height - 1); + } + + // This ensures a proper box is built from the points supplied, no matter the relationship + // between the two points + public static Box from_points(Gdk.Point corner1, Gdk.Point corner2) { + return Box(int.min(corner1.x, corner2.x), int.min(corner1.y, corner2.y), + int.max(corner1.x, corner2.x), int.max(corner1.y, corner2.y)); + } + + public static Box from_center(Gdk.Point center, int width, int height) { + return Box(center.x - (width / 2), center.y - (height / 2), + center.x + (width / 2), center.y + (height / 2)); + } + + public int get_width() { + assert(right >= left); + + return right - left + 1; + } + + public int get_height() { + assert(bottom >= top); + + return bottom - top + 1; + } + + public bool is_valid() { + return (left >= 0) && (top >= 0) && (right >= left) && (bottom >= top); + } + + public bool equals(Box box) { + return (left == box.left && top == box.top && right == box.right && bottom == box.bottom); + } + + // Adjust width, preserving the box's center. + public void adjust_width(int width) { + int center_x = (left + right) / 2; + left = center_x - (width / 2); + right = center_x + (width / 2); + } + + // Adjust height, preserving the box's center. + public void adjust_height(int height) { + int center_y = (top + bottom) / 2; + top = center_y - (height / 2); + bottom = center_y + (height / 2); + } + + public Box get_scaled(Dimensions scaled) { + double x_scale, y_scale; + get_dimensions().get_scale_ratios(scaled, out x_scale, out y_scale); + + int l = (int) Math.round((double) left * x_scale); + int t = (int) Math.round((double) top * y_scale); + + // fix-up to match the scaled dimensions + int r = l + scaled.width - 1; + int b = t + scaled.height - 1; + + Box box = Box(l, t, r, b); + assert(box.get_width() == scaled.width || box.get_height() == scaled.height); + + return box; + } + + public Box get_scaled_similar(Dimensions original, Dimensions scaled) { + double x_scale, y_scale; + original.get_scale_ratios(scaled, out x_scale, out y_scale); + + int l = (int) Math.round((double) left * x_scale); + int t = (int) Math.round((double) top * y_scale); + int r = (int) Math.round((double) right * x_scale); + int b = (int) Math.round((double) bottom * y_scale); + + // catch rounding errors + if (r >= scaled.width) + r = scaled.width - 1; + + if (b >= scaled.height) + b = scaled.height - 1; + + return Box(l, t, r, b); + } + + public Box get_offset(int xofs, int yofs) { + return Box(left + xofs, top + yofs, right + xofs, bottom + yofs); + } + + public Dimensions get_dimensions() { + return Dimensions(get_width(), get_height()); + } + + public void get_points(out Gdk.Point top_left, out Gdk.Point bottom_right) { + top_left = { left, top }; + bottom_right = { right, bottom }; + } + + public Gdk.Rectangle get_rectangle() { + Gdk.Rectangle rect = Gdk.Rectangle(); + rect.x = left; + rect.y = top; + rect.width = get_width(); + rect.height = get_height(); + + return rect; + } + + public Gdk.Point get_center() { + return { (left + right) / 2, (top + bottom) / 2 }; + } + + public Box rotate_clockwise(Dimensions space) { + int l = space.width - bottom - 1; + int t = left; + int r = space.width - top - 1; + int b = right; + + return Box(l, t, r, b); + } + + public Box rotate_counterclockwise(Dimensions space) { + int l = top; + int t = space.height - right - 1; + int r = bottom; + int b = space.height - left - 1; + + return Box(l, t, r, b); + } + + public Box flip_left_to_right(Dimensions space) { + int l = space.width - right - 1; + int r = space.width - left - 1; + + return Box(l, top, r, bottom); + } + + public Box flip_top_to_bottom(Dimensions space) { + int t = space.height - bottom - 1; + int b = space.height - top - 1; + + return Box(left, t, right, b); + } + + public bool intersects(Box compare) { + int left_intersect = int.max(left, compare.left); + int top_intersect = int.max(top, compare.top); + int right_intersect = int.min(right, compare.right); + int bottom_intersect = int.min(bottom, compare.bottom); + + return (right_intersect >= left_intersect && bottom_intersect >= top_intersect); + } + + public Box get_reduced(int amount) { + return Box(left + amount, top + amount, right - amount, bottom - amount); + } + + public Box get_expanded(int amount) { + return Box(left - amount, top - amount, right + amount, bottom + amount); + } + + public bool contains(Gdk.Point point) { + return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom; + } + + // This specialized method is only concerned with resized comparisons between two Boxes, + // of which one is altered in up to two dimensions: (top or bottom) and/or (left or right). + // There may be overlap between the two returned Boxes. + public BoxComplements resized_complements(Box resized, out Box horizontal, out bool horizontal_enlarged, + out Box vertical, out bool vertical_enlarged) { + + bool horizontal_complement = true; + if (resized.top < top) { + // enlarged from top + horizontal = Box(resized.left, resized.top, resized.right, top); + horizontal_enlarged = true; + } else if (resized.top > top) { + // shrunk from top + horizontal = Box(left, top, right, resized.top); + horizontal_enlarged = false; + } else if (resized.bottom < bottom) { + // shrunk from bottom + horizontal = Box(left, resized.bottom, right, bottom); + horizontal_enlarged = false; + } else if (resized.bottom > bottom) { + // enlarged from bottom + horizontal = Box(resized.left, bottom, resized.right, resized.bottom); + horizontal_enlarged = true; + } else { + horizontal = Box(); + horizontal_enlarged = false; + horizontal_complement = false; + } + + bool vertical_complement = true; + if (resized.left < left) { + // enlarged left + vertical = Box(resized.left, resized.top, left, resized.bottom); + vertical_enlarged = true; + } else if (resized.left > left) { + // shrunk left + vertical = Box(left, top, resized.left, bottom); + vertical_enlarged = false; + } else if (resized.right < right) { + // shrunk right + vertical = Box(resized.right, top, right, bottom); + vertical_enlarged = false; + } else if (resized.right > right) { + // enlarged right + vertical = Box(right, resized.top, resized.right, resized.bottom); + vertical_enlarged = true; + } else { + vertical = Box(); + vertical_enlarged = false; + vertical_complement = false; + } + + return BoxComplements.derive(horizontal_complement, vertical_complement); + } + + // This specialized method is only concerned with the complements of identical Boxes in two + // different, spatial locations. There may be overlap between the four returned Boxes. However, + // no portion of any of the four boxes will be outside the scope of the two compared boxes. + public BoxComplements shifted_complements(Box shifted, out Box horizontal_this, + out Box vertical_this, out Box horizontal_shifted, out Box vertical_shifted) { + assert(get_width() == shifted.get_width()); + assert(get_height() == shifted.get_height()); + + bool horizontal_complement = true; + if (shifted.top < top && shifted.bottom > top) { + // shifted up + horizontal_this = Box(left, shifted.bottom, right, bottom); + horizontal_shifted = Box(shifted.left, shifted.top, shifted.right, top); + } else if (shifted.top > top && shifted.top < bottom) { + // shifted down + horizontal_this = Box(left, top, right, shifted.top); + horizontal_shifted = Box(shifted.left, bottom, shifted.right, shifted.bottom); + } else { + // no vertical shift + horizontal_this = Box(); + horizontal_shifted = Box(); + horizontal_complement = false; + } + + bool vertical_complement = true; + if (shifted.left < left && shifted.right > left) { + // shifted left + vertical_this = Box(shifted.right, top, right, bottom); + vertical_shifted = Box(shifted.left, shifted.top, left, shifted.bottom); + } else if (shifted.left > left && shifted.left < right) { + // shifted right + vertical_this = Box(left, top, shifted.left, bottom); + vertical_shifted = Box(right, shifted.top, shifted.right, shifted.bottom); + } else { + // no horizontal shift + vertical_this = Box(); + vertical_shifted = Box(); + vertical_complement = false; + } + + return BoxComplements.derive(horizontal_complement, vertical_complement); + } + + public Box rubber_band(Gdk.Point point) { + assert(point.x >= 0); + assert(point.y >= 0); + + int t = int.min(top, point.y); + int b = int.max(bottom, point.y); + int l = int.min(left, point.x); + int r = int.max(right, point.x); + + return Box(l, t, r, b); + } + + public string to_string() { + return "%d,%d %d,%d (%s)".printf(left, top, right, bottom, get_dimensions().to_string()); + } + + private static bool in_zone(double pos, int zone) { + int top_zone = zone - HAND_GRENADES; + int bottom_zone = zone + HAND_GRENADES; + + return in_between(pos, top_zone, bottom_zone); + } + + private static bool in_between(double pos, int top, int bottom) { + int ipos = (int) pos; + + return (ipos > top) && (ipos < bottom); + } + + private static bool near_in_between(double pos, int top, int bottom) { + int ipos = (int) pos; + int top_zone = top - HAND_GRENADES; + int bottom_zone = bottom + HAND_GRENADES; + + return (ipos > top_zone) && (ipos < bottom_zone); + } + + public BoxLocation approx_location(int x, int y) { + bool near_width = near_in_between(x, left, right); + bool near_height = near_in_between(y, top, bottom); + + if (in_zone(x, left) && near_height) { + if (in_zone(y, top)) { + return BoxLocation.TOP_LEFT; + } else if (in_zone(y, bottom)) { + return BoxLocation.BOTTOM_LEFT; + } else { + return BoxLocation.LEFT_SIDE; + } + } else if (in_zone(x, right) && near_height) { + if (in_zone(y, top)) { + return BoxLocation.TOP_RIGHT; + } else if (in_zone(y, bottom)) { + return BoxLocation.BOTTOM_RIGHT; + } else { + return BoxLocation.RIGHT_SIDE; + } + } else if (in_zone(y, top) && near_width) { + // if left or right was in zone, already caught top left & top right + return BoxLocation.TOP_SIDE; + } else if (in_zone(y, bottom) && near_width) { + // if left or right was in zone, already caught bottom left & bottom right + return BoxLocation.BOTTOM_SIDE; + } else if (in_between(x, left, right) && in_between(y, top, bottom)) { + return BoxLocation.INSIDE; + } else { + return BoxLocation.OUTSIDE; + } + } +} + |