Creating Themes
Overview
Bike themes are created in javascript. The theme defines a list of rules. Each rule is composed of an outline path and a callback function. When Bike needs to style an outline element it:
Starts with a default style object
Constructs an ordered list of matching rules
Passes the style object into each matching rules callback function
Through this process the default style object is transformed into a specific style
Important things to know:
The order that you define your rules is important. Generally you want generic style rules listed first and refinements listed later.
The rule callbacks must be pure functions. Given the same environment and style input values they must always generate the same end style state. They can only read values from the environment and style parameters when deciding what style values to set.
Styles are not inherited from parents as they are in CSS. Instead of setting default styles on a root object you should create a generic rule that matches all elements and sets defaults there. Put this generic rule at the start of your theme and then follow it with more specific rules.
Setup
Themes can get complex! Use these tools to make your life easier:
Use VS Code when editing your themes. This will give you type-checking and autocomplete as you edit your theme. To use VS Code drag and drop your theme folder (not just the index.js file!) onto VS Code's icon. You should see three files listed in VS Code's sidebar:
index.js
– This is where you enter your theme ruleslib.bikeTheme.d.ts
– This is the type definition file. VS Code consults this file to provide autocomplete and to check that you are using the types correctly. You shouldn't edit this file, but it can be useful to read when you need details of what's available.
Use Bike's Window > Outline Path Explorer to understand outline paths and to see what attributes you can match against in your theme. The paths that you use in your theme must all be relative (start with a period).
Use Safari's javascript debugger to set breakpoints in your theme.
Zero to Outline
Let's create a new completely empty theme. Then we'll add in the essential rules that will make your outline look like an outline.
Select Window > Themes > Open Themes
In the Finder duplicate the existing "Basic" theme and name the copy "Demo"
Open "Demo" in VS Code and delete everything that's in the
index.js
file and saveIn Bike select Window > Themes > Demo
Your Bike outline should now look plain text
Things to notice...
While your outline looks plain the outline behavior is still there. You can still expand/collapse lines. You can still insert text. You can still select and edit text, though it will be difficult because you won't see selection marks.
To add back some visual structure add the following rule to your outline. As always, save, and then your open outline should update to reflect the new style rules.
Your outline structure should be visible. This rule matches all rows. This rule is adding padding to all rows in your outline. Parent rows contain their child rows, so the extra padding added to the left edge indents those children.
To see the structure more clearly replace that rule with this rule:
The visual structure of your outline is now be apparent. Rows (blue border) contain text (green border) and potentially other child rows.
The blue and green rectangles are created by attaching decorations to the row and to the row's text. Decorations can also be attached to runs of the row's text.
Adding selection
Let's make it so that you can see what text is selected. To do that we'll create a new kind of rule that matches runs of text. Try adding this rule, and then select some text.
You should see text selections now when you select within a single paragraph. They will disappear when you select multiple paragraphs, but we'll fix that eventually.
A potential question, how would you even know about that @view-selected-range
attribute? This is where the outline path explorer is useful. Select Window > Outline Path Explorer. Then make sure that "Show View Attributes" is selected. Now make some selections.
You should see the @view-selected-range
attribute show up in the outline path explorer when you select a range of text. You can also type .@view-selected-range
into the outline path explorers search field, and then the selected range of text will be highlighted green.
You can use the outline path explorer to find attributes that you can use when styling, and you can also see which elements will be selected by your outline paths when the editor is in various states.
Improving the selection
In our current selection rule we are setting the runs background color directly. This is setting core text's background color attribute. This works, but it's more flexible to use decorations. Replace the selection rule rule with a decoration based approach like this:
This is similar to how we used decorations to draw boxes around rows and row's text. One difference is that in this case we had to set the zPosition
property. We want to make sure that the text selection draws behind the run's text. An alternative design would be to draw in front of the row's text, but make the selection color partially transparent. That could be done like this:
In Bike there are two selection modes–text selection mode and block selection mode. So far we are only showing text selections. When you extend the selection beyond a single paragraph Bike starts using block selection mode. Lets show block selections with this rule:
A few interesting things are happening here.
The matching outline path is calling the
selection
function with ablock
parameter value. This function will return true if the current node has block selection.For this example I am reusing the "background" decoration and changing its style. For example if I give the background rounded corners in my first rule, then the block selection will also have rounded corners. Alternativly I could have created a new decoration with a new ID to indicate block selection.
You might wonder how you could have discovered the selection function on your own... unfortunately the availible functions are not yet document. You'll have to look through the provided theme to see what functions I'm using.
Generally the idea that I'm comming to is that outline paths will use functions to read editor state, things like selection, expanded, collapsed, etc. While outline state row type, row attributes, text content, will be accessed using outline path attributes.
Inline Formatting
Inline formatting such as bold and italic text is applied at the text run level. We use defineRunRule
for this. Most inline formatting is pretty simple. These rules add support for bold and italic text.
While these rules are simple you can add decorations to text runs just like you can add them to rows and row's text. Try creating a rule to show text with the @highlight
attribute. You can add/remove that attribute from text by using Format > Highlight.
More on Decorations
So far we've used decorations as simple backgrounds, but they can do more. They can be placed and sized. They can contain images, symbols, and text. Let's use decorations to style a task item with a checkbox. Start by adding this rule:
With that rule in place when you create a task in your outline it should show up with a red background. Decorations have a contents property that you can use to show images. Change to rule to add a checkbox like this:
We are creating a SF Symbol configuration. Then we use that configuration to create an image. Finally we assign that image to the decorations contents. When you save this new rule you should see an empty checkbox centered over all task rows.
Next we need to position the checkbox in a more reasonable location:
Decorations have x
, y
, width
, and height
properties of type LayoutValue
. You get layout values from the passed in layout
parameter. These are logical values that are resolved later in the layout process to position the decoration.
Dig into lib.bikeTheme.d.ts
to see all possible layout values.
Next we need to make it so that the box is checked when the tasks is done. When a task is marked done that is indicated by adding a @done
attribute to the task's row. This rule matches those done tasks and changes the mark's image.
You can test the task style by selecting a task, press the escape key to enter block selection mode, and then press the spacebar to toggle the done status of the task. The tasks checkbox should toggle!
One more thing.
Add that line to the previous rule and done tasks will get strikethrough text.
Environment
Bike has all sorts of settings. Font, line height, row spacing, etc. It also has different modes such as View > Text Wrap and View > Focus Mode. You'll notice that these things work in the included "Basic" theme, but they don't work in this "Demo" theme that we just created.
To integrate with Bike's settings your theme needs to read those settings out of the environment. For example more themes will want to apply the user's selected font and line height. You can do that by adding these lines to the first .*
rule that we added:
Now when you View > Text Size > Zoom In/Out the outline text size will change in your theme. In addition to user settings the environment also includes system state such as isKey
(does the view have keyboard focus) or isTyping
(true when user is typing, false once they move mouse).
Summary
That's a start. I hope you now know enough to start to understand and modify Bike's default theme, or to create your own more complete themes. Generally you now know all the parts that make up a theme, but the details of how they all fit together can get a bit complex.
I'm still trying to figure out best practices when creating themes. How to organize them and if any additional features are needed. Let me know if you've got any thoughts on how to make things better.
This is likely a bit confusing the first time that you see it. Layout values are
First decorations have a content, and in the end that content is an image. We can create images from four different sources:
Image files and named system images
SF Symbols that are included with macOS (that's what's used above)
Graphics paths (lines, curves, and fill color)
Text given a font, color, and string of text
Key name is arbitrary
They have many possible attributes
They have many possible contents
They have many possible layouts
Last updated