Skip to content

Commit

Permalink
Merge pull request #4 from chaosparrot/feature/walkthrough
Browse files Browse the repository at this point in the history
Feature/walkthrough
  • Loading branch information
chaosparrot authored Oct 16, 2021
2 parents e4bab8d + c9c4959 commit 5c15e20
Show file tree
Hide file tree
Showing 72 changed files with 1,885 additions and 172 deletions.
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ This widget will display the current Talon mode ( Command, dictation or sleep )
The buttons can also be clicked to activate the dwell action immediately.

You can customize the status bar in multiple ways
- You can add the current natural languge by uncommenting line 67 from the display.py file
- You can add the current natural language by uncommenting line 96 from the display.py file
- You can add the microphone toggle by uncommenting line 100 from the display.py file
- You can change the functionality of the icons by changing the activate_statusbar_icon action in the widgets/statusbar.py file all the way at the bottom.

### 2. Event log
Expand Down Expand Up @@ -49,6 +50,12 @@ A context menu can be configured to open on any widget that has mouse clicks ena
This widget contains a bunch of buttons that will interact with the widget that it has opened.
The context menu will attempt to stay on the screen where the right-click was made, and as such will change position accordingly.

### 6. Walkthrough panel

This widget is meant to guide users through a predefined workflow to familiarize them with it.
You can use this to give an interactive experience for users to learn the ins and outs of various workflows.
Included in the Talon HUD is a simple walkthrough for using the Talon HUD itself, but any package can make walkthroughs or workflows.

Voice commands
---

Expand Down Expand Up @@ -100,10 +107,17 @@ If you prefer having a more basic animation free set up, or want to switch back
`head up basic <widget name>` disables animations on the chosen widget.
`head up fancy <widget name>` enables animations on the chosen widget.

Using Talon HUD in your own packages
---

The HUD provides a bunch of hubs like documentation and walkthroughs that you can leverage in your own packages.
That way, if a user has the Talon HUD together with your own package, you can provide documentation and other niceties without having to worry about making your own user interfaces.
Visit the [package enhancement documentation](docs/README.md) for more information.

Updating widgets with content
---

If you want to add your own content to the widgets, visit the [CONTENT PUBLISHING DOCUMENTATION](content/README.md)
If you want to add your own content to the widgets, visit the [content publishing documentation](content/README.md)

Theming
---
Expand Down Expand Up @@ -149,10 +163,7 @@ These are ideas that I want to implement in no specific order and with no specif

Known issues
---
- Line breaks not being properly rendered if they do not contain additional characters
- Lines are improperly spaced height wise when rendering rich text
- Text panel footers on second page seem to have text go through the footer
- Some click dragging issues with the documentation panel where it stays sticky
- Multiple page walkthrough panel does not work properly with text indecis

If any of these ideas seem cool for you to work on, give me a message on the talon slack so we can coordinate stuff.

Expand Down
9 changes: 5 additions & 4 deletions base_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def load(self, dict, initialize = True):

# Set the topic that has claimed this widget
def set_topic(self, topic:str):

self.topic = topic

def set_theme(self, theme):
Expand Down Expand Up @@ -168,7 +167,7 @@ def clear(self):

# Central drawing cycle attached to the canvas
def draw_cycle(self, canvas):
continue_drawing = False
continue_drawing = False

if self.animation_tick != 0:
# Send ticks to the animation method
Expand Down Expand Up @@ -227,8 +226,8 @@ def on_mouse(self, event):
if len(self.drag_position) == 0 and event.event == "mousedown":
self.drag_position = [event.gpos.x - self.limit_x, event.gpos.y - self.limit_y]
elif event.event == "mouseup" and len(self.drag_position) > 0:
self.drag_position = []
self.start_setup("")
self.drag_position = []
if len(self.drag_position) > 0 and event.event == "mousemove":
if self.setup_type != "position":
self.start_setup("position")
Expand Down Expand Up @@ -257,7 +256,8 @@ def start_setup(self, setup_type):
return
# Persist the user preferences when we end our setup
if (self.setup_type != "" and not setup_type):
rect = self.canvas.get_rect()
self.drag_position = []
rect = self.canvas.rect

if (self.setup_type == "position"):
self.preferences.x = int(rect.x) if self.limit_x == self.x else int(rect.x - ( self.limit_x - self.x ))
Expand Down Expand Up @@ -292,6 +292,7 @@ def start_setup(self, setup_type):
actions.user.persist_hud_preferences()
# Cancel every change
elif setup_type == "cancel":
self.drag_position = []
if (self.setup_type != ""):
self.load({}, False)

Expand Down
1 change: 1 addition & 0 deletions commands.talon
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ tag: user.talon_hud_available
^head up show {user.talon_hud_widget_names} on sleep$: user.set_widget_preference(talon_hud_widget_names, "sleep_enabled", 1)
^head up align {user.talon_hud_widget_names} right$: user.set_widget_preference(talon_hud_widget_names, "alignment", "right")
^head up align {user.talon_hud_widget_names} left$: user.set_widget_preference(talon_hud_widget_names, "alignment", "left")
^head up align {user.talon_hud_widget_names} center$: user.set_widget_preference(talon_hud_widget_names, "alignment", "center")
^head up align {user.talon_hud_widget_names} top$: user.set_widget_preference(talon_hud_widget_names, "expand_direction", "down")
^head up align {user.talon_hud_widget_names} bottom$: user.set_widget_preference(talon_hud_widget_names, "expand_direction", "up")

Expand Down
27 changes: 20 additions & 7 deletions content/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ You can publish all kinds of content to the various widgets of the HUD.
For example:
- Status bar icons to display a certain state
- Log messages
- Textual content with options to bold, slant or emphasise in a number of colours
- Textual content with options to bold, slant or emphasize in a number of colours
- Context menu options for various widgets

Publishing content to head up display can be done using actions found in content/state.py.
Expand All @@ -21,7 +21,8 @@ from talon import actions
actions.user.hud_add_log('command', 'This is a log message!')
```
The following values can be added to hud_add_log
- Type: What style we need to use to render the log. Currently supports 'event' for info styling, and 'command' for regular styling.
- Type: What style we need to use to render the log.
Currently supports 'command' for regular styling, 'success', 'warning' and 'error' for various validation messages, and 'event' for notices.
- Message: The log message to display

# Publishing icons to the status bar
Expand All @@ -37,9 +38,19 @@ actions.user.hud_remove_status_icon('my_status_icon')

These are the values that you can give to hud_add_status_icon:
- Identifier: This is a value that uniquely identifies your icon. You can use this to later remove the icon using hud_remove_status_icon
- Image: This is a path to an image. By default, the path 'talon_hud/themes/CURRENT_USER_THEME/IMAGE.png' is assumed if no .png is added. Currently, there is no support of adding images outside of the themes yet.
- Image: This is a path to an image. By default, the path 'talon_hud/themes/CURRENT_USER_THEME/IMAGE.png' is assumed if no .png is added.
However, you can use any path on the system to display an image.
If you are shipping a seperate repository, I recommend making the path relative to the directory where you are running your code from
As that makes no assumptions how the user has built up their talon_user folder.
Something like this:
```
from talon import actions
import os
my_file_dir = os.path.dirname(os.path.abspath(__file__))
icon = my_file_dir + '/image.png'
actions.user.hud_add_status_icon('my_status_icon', icon)
```

TODO - ADD SUPPORT FOR IMAGES OUTSIDE OF TALON_HUD DIRECTORY
TODO - ADD ICONS THAT ARE FUNCTIONAL LIKE THE MODE ICON

# Publishing to a text panel
Expand All @@ -57,6 +68,7 @@ These are the values that you can give to the hud_publish_content action
- Title: This is the header title that will be shown in the text panel. This value will also be used to address the text panel. For example, if you set a title 'Command area', the user will be able to say 'Command area hide' to hide the text panel.
- Show: This is a True or False value. If set to True, this will urge a widget to display and enable itself if it isn't shown yet. If the user has minimized the text panel, it will not be opened. Defaults to True.
- Buttons: These are extra HudButton added to the context menu when the user right clicks the text panel, or like in the example above, says `command area options`.
- Tags: Tags to be enabled while this content is visible on the screen. You can use this to add exploratory voice commands embedded in your text.

# Adding right click buttons to a text panel

Expand All @@ -74,7 +86,7 @@ buttons = [button]
actions.user.hud_publish_content("Text with content", "test", "Test content", True, buttons)
```
Now when the content is published, and extra button can be found in the widgets right click menu. Like the other options in it, when it is shown, the text inside the button is used to activate it, in this case, print to console!
Users can also get accusomed to the option, as it is also available as a quick option. Saying `test content print to console` will activate the button even though the context menu hasn't shown up yet.
Users can also get accustomed to the option, as it is also available as a quick option. Saying `test content print to console` will activate the button even though the context menu hasn't shown up yet.

# Offering choices and options

Expand Down Expand Up @@ -135,15 +147,16 @@ The following styling markers are available:
- <! : Text in the colour orange, used for warning users
- <!! : Text in the colour red, used for errors
- <@ : Text in the colour blue, used to notify the user.
- <cmd@ : Denotes the start of a voice command that can be said - Not all widgets have a specific style for this
- /> : Closing marking - ends the latest style applied

When writing rich text containing voice commands, make sure to emphasise the voice commands with one of these markers so they stand out from the rest of the text.
This makes it easier for the user to quickly pick out the voice commands from the text you have writen.
This makes it easier for the user to quickly pick out the voice commands from the text you have written.
There isn't a firm styling for voice commands yet, so for now just apply a bold marker at the minimum until we maybe decide on one.

# Polling / continuously updating content

For simple usecases, like popping up a single piece of text, just being able to publish content is enough. However, sometimes you want to listen continuously for changes, like changing scopes or updating the list of recent commands.
For simple use cases, like popping up a single piece of text, just being able to publish content is enough. However, sometimes you want to listen continuously for changes, like changing scopes or updating the list of recent commands.
You can do your own event handling if you like, but the HUD also supports a concept called Pollers, some examples can be found in the talon_hud/content folder.

A poller is an object that registered to the Talon HUD, and will be enabled when the user requests it.
Expand Down
66 changes: 66 additions & 0 deletions content/documentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from talon import app, Module, actions, Context
import os

mod = Module()
mod.tag("talon_hud_documentation_overview", desc="Whether or not the documentation overview is on display")
mod.list("talon_hud_documentation_title", desc="List of titles of added documentation in Talon HUD")

ctx = Context()

class HeadUpDocumentation:
order = None
files = None
descriptions = None

def __init__(self):
self.files = {}
self.descriptions = {}
self.order = []

def add_file(self, title: str, description: str, filename: str):
if os.path.isfile(filename):
self.files[title] = filename
if description:
self.descriptions[title] = description

if title not in self.order:
self.order.append(title)
ctx.lists['user.talon_hud_documentation_title'] = self.order
else:
app.notify(filename + " could not be found")

def load_documentation(self, title: str):
if title in self.files:
text_file = open(self.files[title], "r")
documentation = text_file.read()
text_file.close()
actions.user.hud_publish_content(documentation, "documentation", title)

def show_overview(self):
documentation = "Say any of the bolded titles below to open the documentation\n\n"
for index, order in enumerate(self.order):
documentation += "<* " + str(index + 1) + " - " + order + "/>"
if order in self.descriptions:
documentation += ": " + self.descriptions[order]
documentation += "\n"

actions.user.hud_publish_content(documentation, "documentation", "Documentation panel", True, [], ["user.talon_hud_documentation_overview"])

hud_documentation = HeadUpDocumentation()

@mod.action_class
class Actions:

def hud_add_documentation(title: str, description: str, filename: str):
"""Add a file to the documentation panel of the Talon HUD"""
global hud_documentation
hud_documentation.add_file(title, description, filename)

def hud_show_documentation(title: str = ""):
"""Show the general documentation"""
global hud_documentation
if title == "":
hud_documentation.show_overview()
else:
hud_documentation.load_documentation(title)

5 changes: 5 additions & 0 deletions content/documentation.talon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tag: user.talon_hud_available
and tag: user.talon_hud_documentation_overview
-

{user.talon_hud_documentation_title}: user.hud_show_documentation(talon_hud_documentation_title)
61 changes: 20 additions & 41 deletions content/state.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from talon import actions, cron, scope, Module, ui
from talon_init import TALON_USER
from talon.scripting import Dispatch
from user.talon_hud.content.typing import HudPanelContent, HudButton, HudChoice, HudChoices
import time
from typing import Callable, Any
import os

hud_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
max_log_length = 50
mod = Module()

Expand All @@ -23,6 +26,7 @@ class HeadUpDisplayContent(Dispatch):
"status_icons": [],
"log": [],
"abilities": [],
"walkthrough_voice_commands": [],
"topics": {
'debug': HudPanelContent('debug', '', 'Debug panel', [], 0, False),
}
Expand Down Expand Up @@ -90,33 +94,6 @@ def append_to_log(self, type, log_message):

hud_content = HeadUpDisplayContent()

documentation = """
By default, the widgets except for the status bar will hide when Talon goes in sleep mode, but you can keep them around, or hide them, with the following commands.
<*head up show <widget name> on sleep/> keeps the chosen widget enabled during sleep mode.
<*head up hide <widget name> on sleep/> hides the chosen widget when sleep mode is turned on.
On top of being able to turn widgets on and off, you can configure their attributes to your liking.
Currently, you can change the size, position, alignment, animation and font size.
<*head up drag <widget name>/> starts dragging the widget.
<*head up resize <widget name>/> starts resizing the widgets width and height.
<*head up expand <widget name>/> changes the maximum size of the widget in case the content does not fit the regular width and height.
By default these two dimensions are the same so the widget does not grow when more content is added.
<*head up text scale <widget name>/> starts resizing the text in the widget.
<*head up drop/> confirms and saves the changes of your changed widgets.
<*head up cancel/> cancels the changes. Hiding a widget also discards of the current changes.
Some widgets like the event log also allow you to change the text direction and alignment
<*head up align <widget name> left/> aligns the text and the widget to the left side of its bounds.
<*head up align <widget name> right/> aligns the text and the widget to the right side of its bounds.
<*head up align <widget name> top/> changes the direction in which content is placed upwards.
<*head up align <widget name> bottom/> changes the direction in which content is placed downwards.
If you prefer having a more basic animation free set up, or want to switch back to an animated display, you can use the following commands
<*head up basic <widget name>/> disables animations on the chosen widget.
<*head up fancy <widget name>/> enables animations on the chosen widget.
"""

@mod.action_class
class Actions:

Expand All @@ -135,25 +112,32 @@ def hud_add_status_icon(id: str, image: str):
"clickable": False
})

def hud_set_walkthrough_voice_commands(commands: list[str]):
"""Set the voice commands uttered by the user during the walkthrough step"""
global hud_content
hud_content.update({"walkthrough_said_voice_commands": commands})

def hud_remove_status_icon(id: str):
"""Remove an icon to the status bar"""
global hud_content
hud_content.remove_from_set("status_icons", {
"id": id
})

def add_hud_ability(id: str, image: str, colour: str, enabled: bool, activated: bool):
def hud_add_ability(id: str, image: str, colour: str, enabled: int, activated: int, image_offset_x: int = 0, image_offset_y: int = 0):
"""Add a hud ability or update it"""
global hud_content
hud_content.add_to_set("abilities", {
"id": id,
"image": image,
"colour": colour,
"enabled": enabled,
"activated": 5 if activated else 0
"enabled": enabled > 0,
"activated": 5 if activated > 0 else 0,
"image_offset_x": image_offset_x,
"image_offset_y": image_offset_y
})

def remove_hud_ability(id: str):
def hud_remove_ability(id: str):
"""Remove an ability"""
global hud_content
hud_content.remove_from_set("abilities", {
Expand All @@ -165,11 +149,13 @@ def hud_refresh_content():
global hud_content
hud_content.dispatch("content_update", hud_content.content)

def hud_publish_content(content: str, topic: str = '', title:str = '', show:bool = True, buttons: list[HudButton] = None):
def hud_publish_content(content: str, topic: str = '', title:str = '', show:bool = True, buttons: list[HudButton] = None, tags: list[str] = None):
"""Publish a specific piece of content to a topic"""
if buttons == None:
buttons = []
content = HudPanelContent(topic, title, [content], buttons, time.time(), show)
if tags == None:
tags = []
content = HudPanelContent(topic, title, [content], buttons, time.time(), show, tags = tags)

global hud_content
hud_content.publish(content)
Expand All @@ -196,14 +182,7 @@ def hud_publish_choices(choices: HudChoices, title: str = '', content:str = ''):
content = HudPanelContent("choice", title, [content], [], time.time(), True, choices)
global hud_content
hud_content.publish(content)

def hud_get_documentation():
"""Publish a specific piece of content to a topic"""
content = HudPanelContent("documentation", "Head up documentation", [documentation], [], time.time(), True)

global hud_content
hud_content.publish(content)


def show_test_choices():
"""Show a bunch of test buttons to choose from"""
choices = actions.user.hud_create_choices([{"text": "Testing", "image": "next_icon"},{"text": "Another choice"},{"text": "Some other choice"},{"text": "Maybe pick this"},], print)
Expand Down
Loading

0 comments on commit 5c15e20

Please sign in to comment.