# Warmup: .left-column-half[ Recall the ["Door Study"](../wk03/people/vision.html#59) from our Properties of People 1: Vision class. Think, then discuss with your neighbor: do you think this follows the basic ethics of the [Belmont Report](../wk06/menus-experiment.html#53) (Beneficence, Respect for Persons, Justice)? We'll share out in a moment ] .right-column-half[ ![:youtube Video of someone asking for directions while another person walks by with a door,FWSxSQsspiQ] ] --- name: inverse layout: true class: center, middle, inverse --- # Undo reasoning and implementation Lauren Bricker CSE 340 Spring 2022 --- layout: false [//]: # (Outline Slide) # Today's Agenda - Administrivia - Menus part part 5-6, due Thur 12-May @10pm - Menus code will be graded over the weekend - Lauren's OH remote again today (see Ed for link) - Learning goals - Introduce need for Undo by reviewing mental models - Introduce Undo conceptually - Describe Implementation details for assignment --- .left-column40[ ## Reminder: Every system has at least 3 different models ] .right-column60[
graph TD S[System Image:
Your Implementation ] --> |System Feedback | U[User Model: How the user thinks
the system works] D[Design Model: How you
intend the system to work ]-->S U -->|User Feedback | S
] --- # Relating the Human and the Interaction .column[ ![:img Doors with identical handles labeled push and pull, 100%, width](img/undo/doors.jpeg) ] .column[ ![:img Door with what appears to be a push handle labeled pull, 100%, width](img/undo/doors2.jpeg) ] -- .column[ ![:img Don Norman's Human Action Cycle, 100%, width](img/undo/human-action-cycle.png) ] .footnote[[Don Norman, When Three World Collide: A model of the Tangible Interaction Process, 2009](https://www.researchgate.net/publication/221332102_When_three_worlds_collide_A_model_of_the_tangible_interaction_process)] ??? Note to instructors: Need to change image to mermaid --- # Relating the Human and the Interaction .right-column70[ **Gulf of Execution** is the user's belief in the functions the system _doesn't have_ **Gulf of Evaluation** is where the user _doesn't realize the system HAS a functionality_. ] .left-column30[ ![:img Don Norman's Human Action Cycle, 100%, width](img/undo/human-action-cycle.png) ] --- # Better definitions .right-column70[ **Gulf of Execution** is "the gap between a user's goal for action and the means to execute that goal."
1
**Gulf of Evaluation** is the difficulty interpreting the state of a system and how well the interface supports the discovery and interpretation of that state.
2
- "The gulf is small when the system provides information about its state in a form that is easy to get, is easy to interpret, and matches the way the person thinks of the system" - Don Norman ] .left-column30[ ![:img Simplified version Don Norman's Human Action Cycle, 100%, width](img/undo/gulf-gap.png) ] .footnote[
1
[Gulf of Execution](https://en.wikipedia.org/wiki/Gulf_of_execution#cite_note-3)
2
[Gulf of Evaluation](https://en.wikipedia.org/wiki/Gulf_of_evaluation) ] --- # Relating Gulfs and Mental Models .left-column40[ ![:img Blank Procreate interface,75%,width](img/undo/procreate-blank.jpg) ] .right-column60[ How do you use this program? - What "affordances" do you see? - What features do you think exist? - How would you first approach this interface? - What would you try? ] --- # Relating Gulfs and Mental Models .left-column40[ ![:img Procreate interface being explored,75%,width](img/undo/procreate-explore.gif) ] .right-column60[ What are you noticing here? ] --- # Relating Gulfs and Mental Models .left-column40[ ![:img Blank Procreate interface being used,100%,width](img/undo/procreate-explore2.gif) ] .right-column60[ How does a user explore the system? What happens when the user does something they think is core but is really not supported? ] -- count: false .right-column60[ - Need to undo! ] --- class: center, middle, inverse # How do we support Undo? --- layout:false .left-column-half[ ## Remember this? ![:img Picture of interactor hierarchy connected to an interface and a dotted line indicating application interface, 100%,width](img/undo/callbacks.png)] .right-column-half[ Dispatch Strategies - Bottom-first and top-down positional - Focus-based State Machine describes *within-view* response to events ] --- .left-column-half[ ## Event Dispatch ![:img Picture of interactor hierarchy connected to an interface and a dotted line indicating application interface and a do_action() call happening below the line in response to a button_pressed(), 100%,width](img/undo/callbacks2.png)] .right-column-half[ Callbacks handle *application* response to events - Update Application Model ] --- .left-column-half[ ## Event Dispatch ![:img Picture of interactor hierarchy connected to an interface and a dotted line indicating application interface with do_action() replaced with an actionListener, 100%, width](img/undo/callbacks3.png)] .right-column-half[ Callbacks handle *application* response to events - Update Application Model - Best implemented using custom listeners ] --- # What is `ActionPerformed`? `Higher level` input event (`Command` or `Action` object) - Puts some separation between UI and translation objects - Application (or UI) can ‘listen’ for these events: Key advantage: interactors don’t need to know who/what got notification Same basic flow as simple callbacks --- # What should an Action object do? `doAction()` ??? seems like a lot of work when we could just directly do the action. Major reason for action objects -- `undoAction()` ??? What additional information do we need to undo an action? --- # Advantages of an action object .left-column[ - can be stored on an undo stack - can create a consistent abstraction for reversing an action ] .right-column[
classDiagram AbstractAction <|.. AbstractReversibleAction AbstractReversibleAction <|.. ChangeColorAction AbstractReversibleAction <|.. ChangeThicknessAction AbstractReversibleAction <|.. AbstractReversibleViewAction AbstractReversibleViewAction <|.. StrokeAction class AbstractAction { doAction() } class AbstractReversibleAction { +boolean done +undoAction() } class AbstractReversibleViewAction { +invalidate }
] --- # Where do we store actions? A stack .left-column50[
classDiagram AbstractHistory <|.. StackHistory class AbstractHistory { addAction(AbstractReversibleAction action) undo() redo() canUndo() canRedo() } class StackHistory { +capacity: "Max stack size" }
] .right-column50[ Why a stack? ] ??? Consider having some volunteers be actions and have them act it out? --- # Undo and Redo 1. new action object created and `doAction()` called 2. Undo stack updated 3. new action object created and `doAction()` called 4. Undo stack updated 5. `undo()` invoked 6. Undo stack reduced and Redo stack increased 7. `undo()` invoked 8. Undo stack reduced and Redo stack increased 9. `redo()` invoked 10. Redo stack decreased and Undo stack increased 11. new action object created and `doAction()` called 12. Redo stack cleared and Undo stack stack increased ??? draw sequence --- # What if an action can't be undone? Actions that put system into a totally different context (like saving a file) Clear both `undo` *and* `redo` stacks! -- Users may get frustrated with this design --- # Implementing undo() System pops action off undo stack Calls `undoAction()` method on it Pushes it on redo stack --- # Why is undoAction() hard? Two ways to implement `undoAction()`: - *Direct Code* (each action object has custom code) - Need parameters of original action - Better store in `doAction()` for later - This is what we will implement --- # Why is undoAction() hard? Two ways to implement `undoAction()`: - *Direct Code* (each action object has custom code) - *Change Records* (Keep a record of the “old value” for everything changed by the application, then put all those values back to undo) - Like some version control systems - More general - Takes more space - `Action` object records `ChangeRecord` (changes which are abstracted into a common data format) - Application has to provide code to restore from change records --- # Implementing redo() System pops action off redo stack Calls `doAction()` method Pushes it on undo stack --- # More sophisticated forms of Undo .left-column60[ Explicit visualization of steps Manipulation of action list Delete actions from the middle, reorder, etc. by undoing back to point of change then redoing forward But note: `doAction()` must be able to work in new context ] .right-column40[ ![:img Photoshop history tool with 10 different items and the 5th item selected,70%,width](img/undo/photoshop-history.png) ] --- # Flatland: Semantic Undo .left-column30[ ![:img Picture of a map with multiple edits including deleting and adding roads,100%,width](img/undo/flatland-roads.png) ] .right-column60[ ![:img Picture of an undo history with a transaction stack that represents causality in a timeline, 80%,width](img/undo/flatland.png) ] .footnote[ Edwards, W. K. ; Igarashi, T. ; LaMarca, A .; Mynatt, E. D. A temporal model for multi-level undo and redo. UIST 2000, Proceedings of 13th Annual ACM Symposium on User Interface Software and Technology; 2000 November 5-8; San Diego, CA. NY: ACM; 2000; 31-40. ] --- # Discussion of assignment HCI Goals - Modify and existing app in a consistent fashion - Make your modifications accessible - Make your modifications usable Android Goals: - Be able to understand and modify an existing user interface - Learn about floating action buttons - Integrate a custom ColorPicker view - Implement core data structure for Undo Open ended portion: Implement something new --- # Discussion of assignment .left-column50[ ![:youtube Video of assignment, V9_LEi6wLsw] ] .right-column50[ ![:youtube Video of assignment, KKJTSkVnBLc] ] Other example videos on this [playlist](https://www.youtube.com/playlist?list=PLdU5lz7sdq7k5GL82NgOP5lCZzyoZh9wK) --- # Discussion of assignment .left-column[ Like accessibility, you are modifying a fully working program Lots to explore/understand (e.g. FAB buttons) ] -- .right-column[
graph TD M[ReversibleDrawingActivity] --> D[DrawingView] M --> FUndo[FAB:Undo] M --> FRedo[FAB:Redo] M --> FColor[FAB:Color] M --> FThick[FAB:Thickness] FColor --> Red[Red] FColor --> Green[Green] FColor --> Blue[Blue] FThick --> Thin[Thin] FThick --> Med[Med] FThick --> Thick[Thick] classDef normal fill:#e6f3ff,stroke:#333,stroke-width:2px; classDef start fill:#d1e0e0,stroke:#333,stroke-width:4px; class M,D,FColor,FThick,Vis start class Red,Green,Blue,Thin,Med,Thick,Hid normal
] --- # Discussion of assignment .left-column[ DrawingView: Holds strokes FABs: - Green ones are always visible - Purple ones are only visible when active ] .right-column[
graph TD M[ReversibleDrawingActivity] --> D[DrawingView] M --> FUndo[FAB:Undo] M --> FRedo[FAB:Redo] M --> FColor[FAB:Color] M --> FThick[FAB:Thickness] FColor --> Red[Red] FColor --> Green[Green] FColor --> Blue[Blue] FThick --> Thin[Thin] FThick --> Med[Med] FThick --> Thick[Thick] classDef normal fill:#e6f3ff,stroke:#333,stroke-width:2px; classDef start fill:#d1e0e0,stroke:#333,stroke-width:4px; class M,D,FColor,FThick,Vis start class Red,Green,Blue,Thin,Med,Thick,Hid normal
] --- # Code deliverables There are five parts for the coding part of this assignment: - Part 1: Implement `ChangeThicknessAction` - Part 2: Implement history - Part 3: Add a thickness 0 FAB to the thickness menu - Part 4: Integrate colorpicker - Part 5: Your own Undo-able thing --- # What's a FAB? .left-column40[ A FAB is a **Floating Action Button** - Often a single press item on the screen - Occasionally "blows open" to display other FABs ] .right-column60[ ![:img Twitter app FAB in close (on the left) and open (on the right) states, 80%,width](img/undo/twitter-fab.png) ] --- # Defining a FAB in XML ```XML <android.support.design.widget.FloatingActionButton android:id="@+id/fab_thickness_30" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center" android:layout_marginBottom="@dimen/fab_label_margin" android:alpha="0" android:clickable="false" android:contentDescription="@string/thick_desc" android:focusable="true" app:fabSize="mini" app:srcCompat="@drawable/ic_thickness_30" /> ``` --- # Callbacks on a FAB Assing a click listener in the same way you would assign any button ```java // Example lambda setOnClickListener findViewById(R.id.fab_thickness).setOnClickListener((v) ->{ ... }); ``` You should read through `AbstractDrawingActivity#addCollapsibleMenu` and `ReversibleDrawingActivity#toggleMenu` to see how Undo "opens up" a FAB menu