Problem 403825 · medium · Level 04 Non-Linear Data Structures

A Window You Can Drag and Resize

py-properties · invariants · clamping · geometry

A desktop environment lets users drag and resize windows by any edge, or by dragging the centre. Whatever they do, two rules always hold: a window is at least 100 pixels wide and 50 high, and it never leaves the screen. Screen coordinates start at the top-left corner (0, 0), with x growing to the right and y growing downwards.

Write a class Window(left, top, width, height, screen=(1920, 1080)) whose geometry is read and changed through attributes (no method calls):

  • Moving (keeps the size): assigning left, top, or centre (a pair (x, y)). The window moves as asked, but stops at the screen's edges: left ends up between 0 and screen_width - width, and top likewise.
  • Resizing (keeps the top-left corner): assigning width, height, right (= left + width) or bottom (= top + height). The new width is kept between 100 and screen_width - left, and the new height between 50 and screen_height - top.
  • Reading: all of the above, plus centre = (left + width // 2, top + height // 2), area = width * height and box = (left, top, right, bottom). area and box are read-only: assigning them raises AttributeError.
  • The constructor first keeps the size between the minimum and the screen size, then places the window as a move would.
  • snap(where) fills the left half ("left": width screen_width // 2), the right half ("right": from screen_width // 2 to the right edge) or the whole screen ("max"), always at full height.

The tests use the setup's desk(steps, *window_args): it builds Window(*window_args) and runs steps ("set", name, value), ("get", name) or ("snap", where), returning one result per step (None for a set or a snap, and the exception's name if one is raised).

Examples

Input:  desk([("get", "box"), ("set", "left", 1900), ("get", "box"), ("set", "width", 50),
              ("get", "width"), ("set", "centre", (500, 20)), ("get", "box")], 100, 100, 400, 300)
Output: [(100, 100, 500, 400), None, (1520, 100, 1920, 400), None, 100, None,
         (450, 0, 550, 300)]

Input:  desk([("set", "right", 5000), ("get", "width"), ("set", "area", 1), ("snap", "right"), ("get", "box")], 10, 10, 200, 200)
Output: [None, 1910, 'AttributeError', None, (960, 0, 1920, 1080)]

Constraints

  • All numbers are integers; screens are at least 100 x 50.
  • Up to 5000 steps.

Goals

  • Offer several views of the same state (edges, size, centre) as properties
  • Keep an invariant true in every setter, whichever property is assigned
  • Derive every value from a minimal stored state, so nothing goes stale
Starting Python…