name: inverse layout: true class: center, middle, inverse --- # The whole toolkit Lauren Bricker CSE 340 Spring 2020 --- layout:false [//]: # (Outline Slide) # Today's goals - Update from Wednesday - Last bit about Essential Geometry - Let's look at colorpicker - Review and summarize core toolkit architecture --- # Event Dispatch - Top-down, Bottom-Up, Bubble-out, Focused-based: these are theoretical Event dispatch. - Android does it a bit differently. - Capture is top-down: start on the in the biggest interactor that contains the event, then narrow in on which window actually will use the event - Bubbling is bottom-up - start with the window at the front (the last drawn, lowest in the interactor tree) - see if the event is consumed by that interactor. If not, go up the tree. --- # Other comments - layout varies depending on the specific layout container - ConstraintLayouts don't need multiple passes, it's a top down structure. - LinearLayout is post order for measure (measure the children, who first measure their children) - In general, have to look at implementation to know for sure. - `onDraw` is usually pre-order (Intuitively leaf nodes should be 'on top' of parents) - *Capture* is rarely used by component developers - *Bubbling* is far more common - Interface programmers just need to use callbacks, not worry about this stuff --- # Colorpicker assignment --- .left-column[ ## Have now seen *almost* all the parts of an interface ] .right-column[ Input - Input models (events) - Event dispatch - Event handling (state machine) - Callbacks to application Output - Interactor Hierarchy design & use - Drawing models (`onDraw()`) - Layout (`onLayout()` or `XML`) - Damage and redraw process ] --- .left-column-half[ # Core Toolkit Architecture Wait for *Event* *Dispatch* may cause change to: - interactor state - interactor hierarchy - application model ] ??? What do you need to know though? Mostly only if you are a component developer -- .right-column-half[ # Component Developer Implement event handler (e.g. `onTouch()`) Handle *Dispatch* - Update interactor state & model if needed - Notify application if application model should change (with a callback) - Call `invalidate()` ] ??? What about if you are an app developer? Basically, just use callbacks --- .left-column-half[ # Core Toolkit Architecture If damage do - *layout* (may change) - position - size - If damage do *redraw* ] -- .right-column-half[ # Component Developer - May need to implement `onMeasure()` and `onLayout()` (if a container) - Will always implement `onDraw()` but *never call it* (call `invalidate()` instead) ] --- ## View Update: .red[Damage/Redraw] How does the toolkit know what to redraw? What causes damage? ??? concrete example on next slide --- .left-column[ ## View Update: .red[Damage/Redraw] ] .right-column[ What should be redrawn? ![:img google doc with scrollbar, 50%](img/whole/window.png) ] --- .left-column[ ## View Update: .red[Damage/Redraw] ] .right-column[ What should be redrawn? ![:img google doc with scrollbar, 50%](img/whole/window-with-menu.png) ] --- .left-column[ ## View Update: .red[Damage/Redraw] ] .right-column[ What should be redrawn? ![:img google doc with scrollbar, 50%](img/whole/combined.png) ] --- ## View Update: .red[Damage/Redraw] How does the toolkit know what to redraw? What causes damage? ??? Can you think about other things? - Window hidden and re-exposed - Resizing - redrawing --
Naive approach to redraw --- ## View Update: .red[Damage/Redraw] ![:img pic of original screen and changed screen, 72%](img/whole/original-new.png) ??? XXX TODO ADD pic like this using divs? - Can be slow (redrawing everything unecessary) - Can cause flickering - double buffering is better, - hence the 'Canvas' abstraction or equivalent - can then switch which FB is displayed (very fast) --- ## View Update: .red[Damage/Redraw] ![:img pic of original screen and changed screen, 72%](img/whole/original-new.png) ![:img pic with double buffering included, 70%](img/whole/full-solution-buffering.png) --- ## View Update: .red[Damage/Redraw] ![:img pic of original screen and changed screen, 72%](img/whole/original-new.png) ![:img pic with double buffering included, 70%](img/whole/full-solution-buffering.png) **Never** just draw: Why not? ??? - Update *state* - Report *damage* (by calling 'repaint()) - Wait for *toolkit to request redraw* (also works if damage comes from elsewhere) - How does it generalize to any cause of damage (always need state!!) --- ## View Update: .red[Damage/Redraw] How does the toolkit know what to redraw? - Let the component report: Damage/Redraw invoked by `invalidate()` or equivalent --- ## View Update: .red[Damage/Redraw] How does the toolkit know what to redraw? - Let the component report (`invalidate()`) **NOTE** we are *not* calling *onDraw()* directly (important for your assignment) - Aggregate - Usually calculate bounding box --- ## View Update: .red[Draw/Redraw] Virtual device abstraction provided by windowing system Component abstraction provided by toolkit - Enforced using clipping - Supported using coordinate system transformations Drawing is recursive - Makes it possible for parent to *decorate* kids - Parent responsible for making kids think they are the center of the universe (translate) - Clipping: intersect parent and child, also handled by parent ??? Allows each program to (mostly) pretend that it has the screen (frame buffer) to itself Allows each component to (mostly) pretend that it has the screen to itself --- # Core Toolkit Architecture If damage do - *layout* (may change) - position - size - If damage do *redraw* - Union of damage (any of those can cause it) used to trigger redraw of anything inside that union - Drawing + clipping – standard drawing order, but only for things damaged; clipped to damage region - Clear damage --- name: inverse layout: true class: center, middle, inverse --- # Using Essential Geometry as the basis for state --- layout:false # Using Essential Geometry as the basis for state .left-column-half[ ![:img google doc with scrollbar, 80%](img/pps-geom/window.png) ] .right-column-half[ - What is the essence (or nature) of this scrollbar? Where can you interact with it? ] --- # Using Essential Geometry as the basis for state .left-column-half[ ![:img google doc with scrollbar, 80%](img/pps-geom/window.png) ] .right-column-half[ - What is the essence (or nature) of this scrollbar? Where can you interact with it? - on the thumb - inside the scrollbar above the thumb - inside the scrollbar below the thumb - inside up arrow - inside the down arrow ] -- .right-column-half[ - How do we implement a scroll bar like this? ] --- # Scrollbar State machine with Essential Geometry
graph LR S((.)) --> START((START)) START -- "MouseDown:InThumb?StartScroll()" --> SCROLLING((SCROLLING)) START -- "MouseClick:InsideAboveThumb?Scrollup()" --> DONE((DONE)) START -- "MouseClick:InsideBelowThumb?Scrolldown()" --> DONE((DONE)) SCROLLING -- "MouseMove:updateThumbDocument()" --> SCROLLING SCROLLING -- "MouseUp:KeepLocation()" --> DONE linkStyle 0 stroke-width:4px; linkStyle 1 stroke-width:4px; linkStyle 2 stroke-width:4px; linkStyle 3 stroke-width:4px; linkStyle 4 stroke-width:4px; linkStyle 5 stroke-width:4px; class S invisible class START start class SCROLLING normal class DONE finish
--- # Let's try it for a button .left-column-half[ Essential geometry is: - InsideButton - OutsideButton and methods for - `indentButton()` (when button is pressed) - `normalButton()` (when button is not pressed) - `invokeAction()` (when the user releases in the button) - `cancelAction()` (when the user releases outside the button) ] --- # You should have something like this .left-column40[ Essential geometry is: - Inside - Outside and methods for - `indentButton()` (when button is pressed) - `normalButton()` (when button is not pressed) - `invokeAction()` (when the user releases in the button) - `cancelAction()` (when the user releases outside the button) ] .right-column50[
graph TD S((.)) --> START((START)) START -- "DOWN:Inside?indentButton()" --> PRESSED((PRESSED)) PRESSED -- "MOVE:Outside?normalButton()" --> PRESSED PRESSED -- "UP:Outside?cancelAction()" --> END[END] PRESSED -- "UP:Inside?invokeAction()" --> END PRESSED -- "MOVE:Inside?indentButton()" --> PRESSED linkStyle 0 stroke-width:2px; linkStyle 1 stroke-width:2px; linkStyle 2 stroke-width:2px; linkStyle 3 stroke-width:2px; linkStyle 4 stroke-width:2px; linkStyle 5 stroke-width:2px; classDef finish outline-style:double,fill:#d1e0e0,stroke:#333,stroke-width:2px; classDef normal fill:#e6f3ff,stroke:#333,stroke-width:2px; classDef start fill:#d1e0e0,stroke:#333,stroke-width:4px; classDef invisible fill:#FFFFFF,stroke:#FFFFFF,color:#FFFFFF; class S invisible class START start class PRESSED normal class END finish
] --- # How do we implement this? .left-column-half[ - Implement in `onTouch()` using a switch statement - Assume there is an `essentialGeometry(MotionEvent event)` method. It returns a enum that tells you what part of the geometry you are in for that point. - Assume implementations of all the methods - Assume a field, `state` which is the current state of the state machine - enums `EssentialGeometry` and `State` for comparing against ] .right-column-half[ ```java @Override public boolean onTouch(MotionEvent e) { EssentialGeometry geometry = essentialGeometry(event); switch (state) { case State.START: if (geometry == Geometry.INSIDE && e.getAction() == MotionEvent.ACTION_DOWN) { indentButton(); state = State.PRESSED; return true; } break; case PRESSED if (e.getAction() == MotionEvent.ACTION_MOVE) { if (geometry == Geometry.INSIDE) { indentButton(); } else { normalButton(); } return true; } else if (e.getAction() == MotionEvent.ACTION_UP) { state = State.START; // note we don't actually use the END state if (geometry == Geometry.INSIDE) { invokeAction(); } else { cancelAction(); } return true; } break; default: break; } return false; } ``` ] --- name: inverse layout: true class: center, middle, inverse --- # END OF DECK