diff --git a/README.md b/README.md index b557471..cf1896b 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 --- @@ -100,10 +107,17 @@ If you prefer having a more basic animation free set up, or want to switch back `head up basic ` disables animations on the chosen widget. `head up fancy ` 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 --- @@ -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. diff --git a/base_widget.py b/base_widget.py index f173bd1..5d111cc 100644 --- a/base_widget.py +++ b/base_widget.py @@ -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): @@ -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 @@ -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") @@ -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 )) @@ -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) diff --git a/commands.talon b/commands.talon index 847918b..5d1cf54 100644 --- a/commands.talon +++ b/commands.talon @@ -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") diff --git a/content/README.md b/content/README.md index 043e5b5..0e53931 100644 --- a/content/README.md +++ b/content/README.md @@ -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. @@ -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 @@ -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 @@ -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 @@ -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 @@ -135,15 +147,16 @@ The following styling markers are available: - : 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. diff --git a/content/documentation.py b/content/documentation.py new file mode 100644 index 0000000..1a4b926 --- /dev/null +++ b/content/documentation.py @@ -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) + \ No newline at end of file diff --git a/content/documentation.talon b/content/documentation.talon new file mode 100644 index 0000000..10353e4 --- /dev/null +++ b/content/documentation.talon @@ -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) \ No newline at end of file diff --git a/content/state.py b/content/state.py index 983af32..0212a45 100644 --- a/content/state.py +++ b/content/state.py @@ -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() @@ -23,6 +26,7 @@ class HeadUpDisplayContent(Dispatch): "status_icons": [], "log": [], "abilities": [], + "walkthrough_voice_commands": [], "topics": { 'debug': HudPanelContent('debug', '', 'Debug panel', [], 0, False), } @@ -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 on sleep/> keeps the chosen widget enabled during sleep mode. -<*head up hide 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 /> starts dragging the widget. -<*head up resize /> starts resizing the widgets width and height. -<*head up expand /> 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 /> 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 left/> aligns the text and the widget to the left side of its bounds. -<*head up align right/> aligns the text and the widget to the right side of its bounds. -<*head up align top/> changes the direction in which content is placed upwards. -<*head up align 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 /> disables animations on the chosen widget. -<*head up fancy /> enables animations on the chosen widget. -""" - @mod.action_class class Actions: @@ -135,6 +112,11 @@ 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 @@ -142,18 +124,20 @@ def hud_remove_status_icon(id: str): "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", { @@ -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) @@ -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) diff --git a/content/toolkit.py b/content/toolkit.py index bd73ebc..0f8ac12 100644 --- a/content/toolkit.py +++ b/content/toolkit.py @@ -8,6 +8,12 @@ def pick_toolkit_option(data): elif data["text"] == "Microphone selection": actions.user.show_microphone_options() return True + elif data["text"] == "Documentation": + actions.user.hud_show_documentation() + return False + elif data["text"] == "Walkthroughs": + actions.user.hud_show_walkthroughs() + return True mod = Module() @@ -17,6 +23,8 @@ class Actions: def hud_toolkit_options(): """Shows the content available in the HUD toolkit""" choices = actions.user.hud_create_choices([ + {"text": "Documentation"}, + {"text": "Walkthroughs"}, {"text": "Talon scope"}, {"text": "Microphone selection"}, {"text": "Cancel toolkit"} diff --git a/content/toolkit.talon b/content/toolkit.talon index b0870bd..c00ab16 100644 --- a/content/toolkit.talon +++ b/content/toolkit.talon @@ -6,4 +6,5 @@ and tag: user.talon_hud_visible toolkit options$: user.hud_toolkit_options() toolkit scope$: user.hud_toolkit_scope() toolkit microphones$: user.show_microphone_options() -head up documentation: user.hud_get_documentation() \ No newline at end of file +toolkit documentation: user.hud_show_documentation() +toolkit walk throughs: user.hud_show_walkthroughs() \ No newline at end of file diff --git a/content/typing.py b/content/typing.py index 0eef37c..19540a8 100644 --- a/content/typing.py +++ b/content/typing.py @@ -6,6 +6,7 @@ @dataclass class HudRichText: x: int + y: int width: int height: int styles: list[str] @@ -50,4 +51,19 @@ class HudPanelContent: buttons: list[HudButton] published_at: float show: bool - choices: HudChoices = None \ No newline at end of file + choices: HudChoices = None + tags: list[str] = None + +@dataclass +class HudWalkThroughStep: + content: str = '' + context_hint: str = '' + tags: list[str] = None + modes: list[str] = None + app: str = '' + voice_commands: list[str] = None + +@dataclass +class HudWalkThrough: + title: str + steps: list[HudWalkThroughStep] \ No newline at end of file diff --git a/content/walkthrough.py b/content/walkthrough.py new file mode 100644 index 0000000..db2c7a6 --- /dev/null +++ b/content/walkthrough.py @@ -0,0 +1,310 @@ +from talon import app, Module, actions, Context, speech_system, cron, scope +from user.talon_hud.content.typing import HudWalkThrough, HudWalkThroughStep +from user.talon_hud.utils import retrieve_available_voice_commands +import os +import json + +semantic_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +walkthrough_file_location = semantic_directory + "/preferences/walkthrough.csv" +initial_walkthrough_title = "Head up display" + +mod = Module() +mod.tag("talon_hud_walkthrough", desc="Whether or not the walk through widget is on display") +ctx = Context() + +class WalkthroughPoller: + + enabled = False + scope_job = None + walkthroughs = None + walkthrough_steps = None + current_walkthrough = None + current_stepnumber = -1 + current_words = [] + in_right_context = True + + def __init__(self): + self.walkthroughs = {} + self.walkthrough_files = {} + self.walkthrough_steps = {} + self.order = [] + + def enable(self): + if self.enabled == False: + speech_system.register("pre:phrase", self.check_step) + self.scope_job = cron.interval('1500ms', self.display_step_based_on_context) + ctx.tags = ["user.talon_hud_walkthrough"] + self.enabled = True + + def disable(self): + if self.enabled == True: + cron.cancel(self.scope_job) + self.scope_job = None + speech_system.unregister("pre:phrase", self.check_step) + ctx.tags = [] + self.enabled = False + + def load_state(self): + if not os.path.exists(walkthrough_file_location): + self.persist_walkthrough_steps(self.walkthrough_steps) + + fh = open(walkthrough_file_location, "r") + lines = fh.readlines() + fh.close() + + walkthrough_steps = {} + for index,line in enumerate(lines): + split_line = line.strip('\n').split(',') + key = split_line[0] + current_step = split_line[1] + total_step = split_line[2] + walkthrough_steps[key] = {"current": int(current_step), "total": int(total_step)} + self.walkthrough_steps = walkthrough_steps + + # For the initial loading, start the walkthrough if it hasn't been completed fully yet + if initial_walkthrough_title not in self.walkthrough_steps or \ + self.walkthrough_steps[initial_walkthrough_title]['current'] < self.walkthrough_steps[initial_walkthrough_title]['total']: + cron.after('1s', self.start_up_hud) + + def persist_walkthrough_steps(self, steps): + handle = open(walkthrough_file_location, "w") + + walkthrough_items = [] + for key in steps.keys(): + walkthrough_items.append(str(key) + "," + str(steps[key]['current']) + "," + str(steps[key]['total'])) + + if len(walkthrough_items) > 0: + handle.write("\n".join(walkthrough_items)) + + handle.close() + + def start_up_hud(self): + """Start up the HUD - Used for the initial walkthrough""" + actions.user.enable_hud() + cron.after('1s', self.start_initial_walkthrough) + + def start_initial_walkthrough(self): + """Start the initial walkthrough""" + self.start_walkthrough(initial_walkthrough_title) + + def show_options(self): + """Show all the available walkthroughs""" + if len(self.walkthroughs) > 0: + choice_texts = [] + for title in self.walkthroughs: + done = title in self.walkthrough_steps and \ + self.walkthrough_steps[title]['current'] >= self.walkthrough_steps[title]['total'] + choice_texts.append({"text": title, "selected": done}) + choices = actions.user.hud_create_choices(choice_texts, self.pick_walkthrough) + actions.user.hud_publish_choices(choices, "Walkthrough options", + "Pick a walkthrough from the options below by saying <*option /> or by saying the name.") + + def pick_walkthrough(self, data): + """Pick a walkthrough from the options menu""" + self.start_walkthrough(data["text"]) + + def add_walkthrough_file(self, title: str, filename: str): + """Add a file that can be loaded in later as a walkthrough""" + self.walkthrough_files[title] = filename + actions.user.hud_create_walkthrough(title, []) + + def load_walkthrough_file(self, title): + """Load the walkthrough file""" + filename = self.walkthrough_files[title] + walkthrough_defaults = {"content": "", "context_hint": "", "modes": [], "tags": [], "app": ""} + with open(filename) as json_file: + jsondata = json.load(json_file) + steps = [] + if isinstance(jsondata, list): + for unfiltered_step in jsondata: + step = { key: unfiltered_step[key] if key in unfiltered_step else walkthrough_defaults[key] for key in walkthrough_defaults.keys() } + steps.append( actions.user.hud_create_walkthrough_step(**step) ) + + if len(steps) > 0: + actions.user.hud_create_walkthrough(title, steps) + + def add_walkthrough(self, walkthrough: HudWalkThrough): + """Add a walkthrough to the list of walkthroughs""" + if walkthrough.title not in self.order: + self.order.append(walkthrough.title) + self.walkthroughs[walkthrough.title] = walkthrough + + def start_walkthrough(self, walkthrough_title: str): + """Start the given walkthrough if it exists""" + if walkthrough_title in self.walkthroughs: + self.end_walkthrough(False) + self.enable() + + # Preload the walkthrough from the file + if len(self.walkthroughs[walkthrough_title].steps) == 0 and walkthrough_title in self.walkthrough_files: + self.load_walkthrough_file(walkthrough_title) + + self.current_walkthrough = self.walkthroughs[walkthrough_title] + actions.user.enable_hud_id("walk_through") + if walkthrough_title in self.walkthrough_steps: + + # If we have started a walkthrough but haven't finished it - continue where we left off + if walkthrough_title in self.walkthrough_steps and self.walkthrough_steps[walkthrough_title]['current'] < len(self.current_walkthrough.steps): + self.current_stepnumber = self.walkthrough_steps[walkthrough_title]['current'] - 1 + # Otherwise, just start over + else: + self.current_stepnumber = -1 + self.next_step() + + def next_step(self): + """Navigate to the next step in the walkthrough""" + if self.current_walkthrough is not None: + + # Update the walkthrough CSV state + if self.current_walkthrough.title not in self.walkthrough_steps: + self.walkthrough_steps[self.current_walkthrough.title] = {"current": 0, "total": len(self.current_walkthrough.steps)} + self.walkthrough_steps[self.current_walkthrough.title]['current'] = self.current_stepnumber + 1 + self.persist_walkthrough_steps(self.walkthrough_steps) + self.current_words = [] + + if self.current_stepnumber + 1 < len(self.current_walkthrough.steps): + self.transition_to_step(self.current_stepnumber + 1) + self.walkthrough_steps[self.current_walkthrough.title]['current'] = self.current_stepnumber + else: + self.end_walkthrough() + + def previous_step(self): + """Navigate to the previous step in the walkthrough""" + if self.current_walkthrough is not None: + + # Update the walkthrough CSV state + self.walkthrough_steps[self.current_walkthrough.title]['current'] = max(0, self.current_stepnumber - 1) + self.persist_walkthrough_steps(self.walkthrough_steps) + self.current_words = [] + + if self.current_stepnumber - 1 >= 0: + self.transition_to_step(max(0, self.current_stepnumber - 1)) + self.walkthrough_steps[self.current_walkthrough.title]['current'] = self.current_stepnumber + + + def transition_to_step(self, stepnumber): + """Transition to the next step""" + self.current_stepnumber = stepnumber + self.display_step_based_on_context(True) + + def end_walkthrough(self, hide: bool = True): + """End the current walkthrough""" + + # Persist the walkthrough as done + if hide: + self.walkthrough_steps[self.current_walkthrough.title]['current'] = len(self.current_walkthrough.steps) + self.persist_walkthrough_steps(self.walkthrough_steps) + actions.user.hud_set_walkthrough_voice_commands([]) + actions.user.hud_publish_content("No walk through started", "walk_through") + actions.user.disable_hud_id("walk_through") + actions.user.hud_add_log("event", "Finished the \"" + self.current_walkthrough.title + "\" walkthrough!") + + self.disable() + self.current_walkthrough = None + self.current_stepnumber = -1 + self.in_right_context = False + + def is_in_right_context(self): + """Check if we are in the right context for the step""" + in_right_context = True + if self.current_walkthrough is not None: + if self.current_stepnumber < len(self.current_walkthrough.steps): + step = self.current_walkthrough.steps[self.current_stepnumber] + tags = scope.get('tag') + modes = scope.get('mode') + app_name = scope.get('app')['name'] + in_correct_tags = set(step.tags) <= tags + in_correct_modes = set(step.modes) <= modes + in_correct_app = True + in_correct_app = step.app == "" or step.app in app_name + in_right_context = in_correct_tags and in_correct_modes and in_correct_app + return in_right_context + + def display_step_based_on_context(self, force_publish = False): + """Display the correct step information based on the context matching""" + in_right_context = self.is_in_right_context() + if self.in_right_context != in_right_context or force_publish: + step = self.current_walkthrough.steps[self.current_stepnumber] + if not in_right_context: + actions.user.hud_publish_content(step.context_hint, "walk_through") + else: + # Forced publication must clear the voice commands said as well + # To ensure a clean widget state + if force_publish: + actions.user.hud_set_walkthrough_voice_commands([]) + + actions.user.hud_publish_content(step.content, "walk_through") + self.in_right_context = in_right_context + + def check_step(self, phrase): + """Check if contents in the phrase match the voice commands available in the step""" + if self.current_walkthrough is not None and self.is_in_right_context(): + phrase_to_check = " ".join(phrase['phrase']).lower() + if self.current_stepnumber < len(self.current_walkthrough.steps): + step = self.current_walkthrough.steps[self.current_stepnumber] + + current_length = len(self.current_words) + for voice_command in step.voice_commands: + if voice_command in phrase_to_check and voice_command not in self.current_words: + self.current_words.append(voice_command) + + # Send an update about the voice commands said during the step if it has changed + if current_length != len(self.current_words): + actions.user.hud_set_walkthrough_voice_commands(list(self.current_words)) + + +hud_walkthrough = WalkthroughPoller() +hud_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +def load_walkthrough(): + global hud_walkthrough + actions.user.hud_add_walkthrough('Head up display', hud_directory + '/docs/hud_walkthrough.json') + actions.user.hud_add_poller('walk_through', hud_walkthrough) + hud_walkthrough.load_state() + +app.register('ready', load_walkthrough) + +@mod.action_class +class Actions: + + def hud_add_walkthrough(title: str, filename: str): + """Add a walk through through a file""" + global hud_walkthrough + hud_walkthrough.add_walkthrough_file(title, filename) + + def hud_create_walkthrough_step(content: str, context_hint: str = '', tags: list[str] = None, modes: list[str] = None, app: str = ''): + """Create a step for a walk through""" + voice_commands = retrieve_available_voice_commands(content) + tags = [] if tags is None else tags + modes = [] if modes is None else modes + return HudWalkThroughStep(content, context_hint, tags, modes, app, voice_commands) + + def hud_create_walkthrough(title: str, steps: list[HudWalkThroughStep]): + """Create a walk through with all the required steps""" + global hud_walkthrough + hud_walkthrough.add_walkthrough(HudWalkThrough(title, steps)) + + def hud_start_walkthrough(title: str): + """Starts a loaded in walk through""" + global hud_walkthrough + hud_walkthrough.start_walkthrough(title) + + def hud_skip_walkthrough_step(): + """Skip the current walk through step""" + global hud_walkthrough + hud_walkthrough.next_step() + + def hud_previous_walkthrough_step(): + """Skip the current walk through step""" + global hud_walkthrough + hud_walkthrough.previous_step() + + def hud_skip_walkthrough_all(): + """Skip the current walk through step""" + global hud_walkthrough + hud_walkthrough.end_walkthrough() + + def hud_show_walkthroughs(): + """Show all the currently available walk through options""" + global hud_walkthrough + hud_walkthrough.show_options() \ No newline at end of file diff --git a/content/walkthrough.talon b/content/walkthrough.talon new file mode 100644 index 0000000..41fa227 --- /dev/null +++ b/content/walkthrough.talon @@ -0,0 +1,19 @@ +tag: user.talon_hud_available +and tag: user.talon_hud_visible +and tag: user.talon_hud_walkthrough +- +read more: user.increase_widget_page("walk_through") +skip step: + sleep(1.5) + user.hud_skip_walkthrough_step() +skip everything: user.hud_skip_walkthrough_all() +previous step: + user.hud_previous_walkthrough_step() +^head up theme dark$: + sleep(0.5) + user.switch_hud_theme('dark') +^head up theme light$: + sleep(1.5) + user.switch_hud_theme('light') +^music and video playlist$: + user.open_url("https://www.youtube.com/watch?v=lyVICt4vdB0&list=PLEelxuGt2Io5jGNnA44S9lRhclhz7po1U&index=1") \ No newline at end of file diff --git a/display.py b/display.py index 474ae2f..cc91ca8 100644 --- a/display.py +++ b/display.py @@ -16,13 +16,14 @@ from user.talon_hud.widgets.textpanel import HeadUpTextPanel from user.talon_hud.widgets.choicepanel import HeadUpChoicePanel from user.talon_hud.widgets.documentationpanel import HeadUpDocumentationPanel +from user.talon_hud.widgets.walkthroughpanel import HeadUpWalkThroughPanel from user.talon_hud.widgets.contextmenu import HeadUpContextMenu from user.talon_hud.content.typing import HudPanelContent, HudButton from user.talon_hud.content.poller import Poller from user.talon_hud.utils import string_to_speakable_string # Taken from knausj/code/numbers to make Talon HUD standalone -# The numbers should realistically stay very low for choices, because you dont want choice overload for the user, up to 100 +# The numbers should realistically stay very low for choices, because you don't want choice overload for the user, up to 100 digits = "zero one two three four five six seven eight nine".split() teens = "ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen".split() tens = "twenty thirty forty fifty sixty seventy eighty ninety".split() @@ -79,6 +80,9 @@ def __init__(self, display_state, preferences): # Extra text boxes can be defined to be assigned to different topics # HeadUpTextPanel('Text box two', self.preferences.prefs, self.theme, {'topics': ['your_topic_here'], 'current_topic': 'your_topic_here'}), HeadUpChoicePanel('Choices', self.preferences.prefs, self.theme, {'topics': ['choice'], 'current_topic': 'choice'}), + + HeadUpAbilityBar('ability_bar', self.preferences.prefs, self.theme), + HeadUpWalkThroughPanel('walk_through', self.preferences.prefs, self.theme, {'topics': ['walk_through']}), # Special widgets that have varying positions HeadUpContextMenu('context_menu', self.preferences.prefs, self.theme), @@ -92,11 +96,17 @@ def __init__(self, display_state, preferences): self.keep_alive_pollers = ['status', 'history'] # Uncomment the line below to add language icons by default - # self.subscribe_content_id('status_bar', 'language') + # self.subscribe_content_id('status_bar', 'language') def start(self): + # Uncomment the line below to add the single click mic toggle by default + # actions.user.hud_add_single_click_mic_toggle() + if (self.preferences.prefs['enabled']): - self.enable() + self.enable() + + if actions.sound.active_microphone() == "None": + actions.user.hud_add_log("warning", "Microphone is set to 'None'!\n\nNo voice commands will be registered.") def enable(self, persisted=False): if not self.enabled: @@ -160,7 +170,11 @@ def enable_id(self, id): if not widget.enabled and widget.id == id: widget.enable(True) if widget.topic in self.pollers and not self.pollers[widget.topic].enabled: - self.pollers[widget.topic].enable() + self.pollers[widget.topic].enable() + + if isinstance(widget, HeadUpTextPanel) and widget.panel_content.tags != None \ + and len(widget.panel_content.tags) > 0: + self.update_context() def disable_id(self, id): for widget in self.widgets: @@ -168,6 +182,10 @@ def disable_id(self, id): widget.disable(True) if widget.topic in self.pollers and widget.topic not in self.keep_alive_pollers: self.pollers[widget.topic].disable() + + if isinstance(widget, HeadUpTextPanel) and widget.panel_content.tags != None \ + and len(widget.panel_content.tags) > 0: + self.update_context() self.determine_active_setup_mouse() def subscribe_content_id(self, id, content_key): @@ -449,6 +467,11 @@ def update_context(self): if choice_title: for widget_name in current_widget_names: quick_choices[widget_name + " " + choice_title] = widget.id + "|" + str(index) + + # Add tags set for specific content on display + if widget.enabled and isinstance(widget, HeadUpTextPanel) and widget.panel_content.tags is not None: + for index, tag in enumerate(widget.panel_content.tags): + tags.append(tag) # Add context choices if widget.enabled and isinstance(widget, HeadUpContextMenu): diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..79ac181 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,53 @@ +Package enhancement +==== + +Currently, there are two main ways to enhance your own packages with Talon HUD, namely documentation and walkthroughs. +Both of them work by registering files to the Talon HUD if it is available, so it won't cause slowdowns or crashes for non-Talon HUD users. + +For an example on how to add stuff, you can look at the file [load_talon_hud_docs.py](load_talon_hud_docs.py) in the docs folder. +It is recommended that you have a separate directory where you keep all the files related to your documentation or walkthroughs. + +For all of these options, you should have a look at the part about rich text in the [content publishing documentation](content/README.md) as it gives an outline of all the possible rich text tokens you can use. +You should think ahead if the explanation you are making for the users is meant to be a quick reference, or an exploration of the options. +A documentation page is easier to scan, so it lends itself well for a quick reference. Whereas a walkthrough is harder to scan, but much easier to process, as it let's the user focus on one thing at a time. + +# Documentation + +To add documentation to the Talon HUD toolkit, you can use the `user.hud_add_documentation` action. + +These are the values that you can give to the `user.hud_add_documentation` action. +- Title: The title of the documentation you're adding. This will be shown as a header in the panel showing when a user accesses the documentation either by navigating to it or by saying `toolkit documentation`. +- Description: A short description of what the documentation is about. This will be shown next to the title in the documentation panel. +- Filename: An absolute path to the file containing the documentation. + +For an example, you can have a look at the [hud_widget_documentation.txt](hud_widget_documentation.txt) file in the docs directory. + +# Walkthroughs + +A walkthrough is a step by step guide to give the user a sense of what kind of things they can do with your package. +Every step can contain one or more voice commands that the user must say before the step is advanced. +Every step can have a specific context linked to it as well, so that the user is guided towards the right situation to try out the voice commands. +As a bonus, each walkthrough the user has finished will be marked with a checkmark on the walkthrough overview. + +Talon HUD uses one to give a brief tour of the possibilities and the content. + +Adding a walkthrough to the `toolkit walkthrough` panel can be done using the `user.hud_add_walktrough` action. +This action takes only two values. +- Title: The title of the walkthrough that is being added, this will show up in the options panel. +- Filename: An absolute path to the JSON file containing the walkthrough data. + +As an example JSON, you can have a look at the hud_walkthrough.json file in the docs directory. + +Basically, the file is built up as a list of steps. Each step can contain the following information. + +``` +{ + "content": "", # The text of the walkthrough step, with to denote voice commands that can be said. + "context_hint": "", # The text that is displayed when the user is in the wrong context, to guide them back on the right place where the voice commands are active. + "tags": [], # A list of tags that must be active, otherwise the context_hint will be shown to guide the user back to the right tags. + "modes": [], # A list of modes that must be active, otherwise the context_hint will be shown to guide the user back to the right modes. + "app": "" # The name of the app that the user must be in, otherwise the context_hint will be shown. +} +``` + +If you are unsure if your JSON formatting is correct, you can test it on [jsonlint.com](https://jsonlint.com/) for errors. \ No newline at end of file diff --git a/docs/basic_browser_usage_walkthrough.json b/docs/basic_browser_usage_walkthrough.json new file mode 100644 index 0000000..50723ee --- /dev/null +++ b/docs/basic_browser_usage_walkthrough.json @@ -0,0 +1,80 @@ +[ + { + "content": "Now that you've opened a browser, you can learn the basics. First up, navigating to a website! Say to select the address bar.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to start the walkthrough." + }, + { + "content": "In here, you can type any website you want. Let's navigate to the talon voice website with and . If you make a mistake, just say go address and retry.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "Of course, it would be annoying to type out every address by voice, luckily we can also go to specific websites with a voice command. for example navigates over to youtube.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "Returning to the homepage of our browser is done with . We can also navigate back to youtube by saying , and forward again by saying .", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "You can also open a new tab with the open command, opens a new youtube tab. We can see new recommendations with , as it reloads the page to look for new ones.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "Let's start searching for stuff! Some websites are available for search commands, like google! Saying will open up a new tab searching for restaurants!", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "There's also wikipedia searches available, so will search for that neat blue bird that fishes from a branch.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "You should have a bunch of tabs open now, which we can navigate between. will go to your previous tab, and goes to the wikipedia page.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "You can also navigate to a specific tab by saying a tab number, for example, will go back to the youtube tab.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "Opening and closing new tabs can be done with and . If you accidentally closed a tab, you can also say to get it back! ", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "If you're brave enough to do your private browsing with voice commands, you can say to open a new session. ", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "In order to get back out of your private browsing session, you can simply say to close all the tabs at the same time.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + }, + { + "content": "That concludes the basic usage walkthrough. If you ever need a refresher, you can go to and then say for a full overview.", + "modes": ["command"], + "tags": ["browser"], + "context_hint": "Please open a browser application like Firefox, Google Chrome, Edge or Safari to resume the walkthrough." + } +] \ No newline at end of file diff --git a/docs/browser_usage_documentation.txt b/docs/browser_usage_documentation.txt new file mode 100644 index 0000000..596dd33 --- /dev/null +++ b/docs/browser_usage_documentation.txt @@ -0,0 +1,31 @@ +<*Going to websites/> + +There are a bunch of ways to navigate to a URL. +<*go address/> selects the address bar, where you can start dictating a website to go to. +<*go to /> goes to a website in the current tab. +<*open /> opens a new tab with the given website. + +The list of websites you can go to with the <*go to/> and <*open/> commands can be managed in the websites.csv file in knausj_talon/settings. + +<*Tab navigation/> + +<*tab open/> opens a new browser tab. +<*tab next/> opens the tab to the right of the current tab. +<*tab last/> opens the tab to the left of the current tab. +<*go tab one/> opens the first tab, and any small number will open that specific tab. +<*tab close/> closes the current browser tab. +<*tab reopen/> opens the previously closed tab. + +<*Bookmarks/> + +<*bookmark it/> bookmarks the current page. +<*bookmark show/> opens all the available bookmarks. +In Google chrome, you can then use <*tab/> to move over to the list of the bookmarks, and then select a bookmark with <*go up, go down/> and <*enter/>. +Your mileage may vary in other browsers with this. + +<*Searching using a search engine/> + +<* hunt /> will search for the given user text by opening a new tab and searching with the given search engine. +<* hunt this/> will use the current selection to do the search. + +The available search engines can be managed in the search_engines.csv file located in the knausj_talon/settings folder. \ No newline at end of file diff --git a/docs/dictation_walkthrough.json b/docs/dictation_walkthrough.json new file mode 100644 index 0000000..dcac9fe --- /dev/null +++ b/docs/dictation_walkthrough.json @@ -0,0 +1,133 @@ +[ + { + "content": "There are multiple ways to dictate text in Talon. For this walkthrough, we will use the draft editor. This window can be opened by saying ", + "modes": ["command"], + "context_hint": "Please turn on command mode to continue the walkthrough." + }, + { + "content": "The draft editor is an overlay which you can use to type or dictate text, before submitting it to another application. hides the window in case you do not want it.", + "modes": ["command"], + "context_hint": "Please turn on command mode to continue the walkthrough." + }, + { + "content": "Say to open it again. In order to start dictating here, we can turn on dictation mode by saying ." + }, + { + "content": "Now every word you say will be typed out. In order to return to regular commands, say .", + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "Let's clear our draft window first by saying . Now we have a clean slate to try other things, like ", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "The <*say/> command dictates all the words that come after it while staying in command mode. Saying and will place a dot and enter a new line.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "There is also a word equivalent to the <*say/> command. Try saying to test it out.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "Some basic punctuation can come in handy for this type of thing as well. and for example will place ?!", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "In case we ever make a mistake in dictating, we can do a bunch of things. will undo the last command, which was placing the exclamation mark.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "We can also call the backspace button directly. Try saying to get rid of the question mark.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "Another way is to say . That will press backspace for as long as the last said phrase, in this case, the word really.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "In the draft window, we can do all kinds of corrections as well. For example, will remove the word above the red letter c.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "You can also replace the word directly. will undo the clearing, and will replace the word is with was. ", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "There are other ways we can do corrections in the draft window, but we'll focus on moving the cursor to the start of the sentence now. will do that for us.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": " will place a \" mark here, you might have to say <*space/> as well if it doesn't appear. Saying will go to the back of the sentence.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "Let's start combining some commands. should place \", where we are now.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "We can also combine the <*say/> command with punctuation marks. will place a comma after our said words.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "<*over/> puts a stop to a lot of the phrase commands, so you can make one large sentence rather than waiting a bunch. will also place an enter, just like <*enter/> does.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "There are other ways to quote something as well using something called formatters. will quote the sentence for you.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "There are all kinds of formatters available for text, like ones that capitalize the first letter. Let's replace the frog word next. will select it for us.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "Finally, let's try out some formatters. will result in Frog, to remove it, and will place FROG.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "If you are happy with the result, you can say to send the created text over to the next application.", + "modes": ["command"], + "tags": ["user.draft_window_showing"], + "context_hint": "Please select the draft editor by clicking on it." + }, + { + "content": "A refresher of these commands concepts can be found in in the <*talon draft window/> and <*text formatting/> sections.", + "modes": ["command"], + "context_hint": "Please select the draft editor by clicking on it." + } +] \ No newline at end of file diff --git a/docs/hud_walkthrough.json b/docs/hud_walkthrough.json new file mode 100644 index 0000000..a91542d --- /dev/null +++ b/docs/hud_walkthrough.json @@ -0,0 +1,73 @@ +[ + { + "content": "Welcome to the Talon HUD, a unified collection of widgets! This is a short walkthrough to the features it offers. Say to continue to the next step. \nIf you do not want to continue this walkthrough, right click here and select <*Mark as done/>.", + "modes": ["command"], + "context_hint": "Please turn on command modes to continue the walkthrough" + }, + { + "content": "Talon HUD is built up using multiple widgets which can be hidden and shown as desired. On the bottom right, whenever you say a command, the <*event log/> widget will show a message.\nTry saying to hide it, and to make it appear again!", + "modes": ["command"], + "context_hint": "Please turn on command modes to continue the walkthrough" + }, + { + "content": "Just below the <*event log/>, you can see the second widget, the <*status bar/>. The <*status bar/> is a widget that offers a quick glance into what state Talon currently is in. Try saying to see what happens!" + }, + { + "content": "As you can see, talon is now asleep, as indicated by the moon shape. should get you right back in your command modes!" + }, + { + "content": "We can change the appearance of the widgets like the <*status bar/> on the fly if we want to. will turn on dark modes, which I like to use a lot, but if you want to change it back you can just say again.", + "modes": ["command"], + "context_hint": "Please turn on command modes to continue the walkthrough" + }, + { + "content": "The <*status bar/>, like every widget, has options that can be shown by either right clicking, or saying the widget name and the word options to show them. In this case, this would be ", + "modes": ["command"], + "context_hint": "Please turn on command modes to continue the walkthrough" + }, + { + "content": "In this list, you can see a bunch of choices which you can click. In this case, we want to add the microphone toggle by saying ", + "modes": ["command"], + "context_hint": "Please turn on command modes to continue the walkthrough" + }, + { + "content": "You now have a one-click toggle for the microphone listening for Talon. You can remove this by right clicking the <*status bar/> and selecting remove microphone, but you can also say as every option in the option menu can be directly addressed using