diff --git a/scripts/generate_visual_guide.py b/scripts/generate_visual_guide.py new file mode 100644 index 0000000..6070b19 --- /dev/null +++ b/scripts/generate_visual_guide.py @@ -0,0 +1,579 @@ +import abc +import collections +from pathlib import Path + +import jinja2 + +from qtile_bonsai.core.nodes import Node, Pane +from qtile_bonsai.core.tree import Tree +from qtile_bonsai.core.utils import to_snake_case + + +jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader("templates"), autoescape=True +) + +index_tmpl_path = Path("visual_guide/index.template.html") +index_html_path = Path("static/visual_guide/index.html") + +_examples_registry: list[type["Example"]] = [] + + +class ExamplePane(Pane): + label: str + focused: bool + min_size: int = 10 + + +class ExampleTree(Tree): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.command: str | None = None + self._pane_label_seq = "A" + + def focus(self, pane: ExamplePane): + super().focus(pane) + for p in self.iter_panes(): + p.focused = False + pane.focused = True + + def create_pane(self, *args, **kwargs) -> ExamplePane: + p = super().create_pane(*args, **kwargs) + ep = ExamplePane( + p.principal_rect, + margin=p.box.margin, + border=p.box.border, + padding=p.box.padding, + ) + ep.id = p.id + ep.label = self._get_next_pane_label() + self.focus(ep) + return ep + + def clone(self) -> "ExampleTree": + Node.reset_id_seq() + return super().clone() + + def _get_next_pane_label(self): + label = self._pane_label_seq + self._pane_label_seq = chr(ord(self._pane_label_seq) + 1) + return label + + +def make_tree(): + Node.reset_id_seq() + tree = ExampleTree(300, 200) + tree.set_config("tab_bar.hide_when", "single_tab") + tree.set_config("tab_bar.height", 15) + return tree + + +class Example(metaclass=abc.ABCMeta): + section: str = "" + template_path: str = "visual_guide/examples/1_to_n.template.html" + + @abc.abstractmethod + def build_context_fragment(self) -> dict: + pass + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + if cls not in _examples_registry: + _examples_registry.append(cls) + + @classmethod + def id(cls): + return to_snake_case(cls.__name__.partition("Eg")[2]) + + def build_context(self) -> dict: + cxt = { + "id": self.id(), + "template_path": self.template_path, + } + cxt.update(self.build_context_fragment()) + return cxt + + +class EgSplits1(Example): + section = "Splits" + + def build_context_fragment(self): + lhs = make_tree() + lhs.tab() + + rhs1 = lhs.clone() + rhs1.split(rhs1.node(4), "x") + rhs1.command = 'spawn_split(program, "x")' + + rhs2 = lhs.clone() + rhs2.split(rhs2.node(4), "y") + rhs2.command = 'spawn_split(program, "y")' + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2], + } + + +class EgTabs1(Example): + section = "Tabs" + + def build_context_fragment(self): + lhs = make_tree() + lhs.tab() + + rhs = lhs.clone() + rhs.tab() + rhs.command = "spawn_tab(program)" + + return { + "lhs": lhs, + "rhs_items": [rhs], + } + + +class EgTabs2(Example): + section = "Tabs" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + + rhs = lhs.clone() + rhs.tab(rhs.node(lp3.id), new_level=True) + rhs.command = "spawn_tab(program, new_level=True)" + + return { + "lhs": lhs, + "rhs_items": [rhs], + } + + +class EgTabs3(Example): + section = "Tabs" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + lp4 = lhs.tab(lp3, new_level=True) + + rhs1 = lhs.clone() + rhs1.tab(rhs1.node(lp4.id)) + rhs1.command = "spawn_tab(program)" + + rhs2 = lhs.clone() + rhs2.tab(rhs2.node(lp4.id), level=1) + rhs2.command = "spawn_tab(program, level=1)" + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2], + } + + +class EgMergeToSubtab1(Example): + section = "Merge to Subtab" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "y") + lp3 = lhs.split(lp2, "x") + _ = lhs.split(lp3, "x", normalize=True) + lhs.focus(lp3) + + rhs1 = lhs.clone() + rhs1.merge_with_neighbor_to_subtab(rhs1.node(lp3.id), "right", normalize=True) + rhs1.command = 'merge_to_subtab("right")' + rhs1.focus(rhs1.node(lp3.id)) + + rhs2 = lhs.clone() + rhs2.merge_with_neighbor_to_subtab(rhs2.node(lp3.id), "left", normalize=True) + rhs2.command = 'merge_to_subtab("left")' + rhs2.focus(rhs2.node(lp3.id)) + + rhs3 = lhs.clone() + rhs3.merge_with_neighbor_to_subtab(rhs3.node(lp3.id), "up", normalize=True) + rhs3.command = 'merge_to_subtab("up")' + rhs3.focus(rhs3.node(lp3.id)) + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2, rhs3], + } + + +class EgMergeToSubtab2(Example): + section = "Merge to Subtab" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "y") + lp3 = lhs.split(lp2, "x") + lp4 = lhs.split(lp3, "x", normalize=True) + _ = lhs.tab(lp4, new_level=True) + lhs.focus(lp3) + + rhs1 = lhs.clone() + rhs1.merge_with_neighbor_to_subtab(rhs1.node(lp3.id), "right", normalize=True) + rhs1.command = 'merge_to_subtab("right")' + rhs1.focus(rhs1.node(lp3.id)) + + return { + "lhs": lhs, + "rhs_items": [rhs1], + } + + +class EgPushIn1(Example): + section = "Push In" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x", ratio=0.33) + lp3 = lhs.split(lp2, "y") + lp4 = lhs.split(lp3.parent, "x") + _ = lhs.split(lp4, "y") + lhs.focus(lp3) + + rhs1 = lhs.clone() + rhs1.push_in_with_neighbor( + rhs1.node(lp3.id), + "right", + src_selection="mru_deepest", + dest_selection="mru_largest", + normalize=True, + ) + rhs1.command = 'push_in("right")' + rhs1.focus(rhs1.node(lp3.id)) + + rhs2 = lhs.clone() + rhs2.push_in_with_neighbor( + rhs2.node(lp3.id), + "left", + src_selection="mru_deepest", + dest_selection="mru_deepest", + ) + rhs2.command = 'push_in("left")' + rhs2.focus(rhs2.node(lp3.id)) + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2], + } + + +class EgPullOut1(Example): + section = "Pull Out" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + lp4 = lhs.split(lp3, "x") + lp5 = lhs.split(lp4, "y") + lhs.focus(lp5) + + rhs1 = lhs.clone() + rhs1.pull_out(rhs1.node(lp5.id), normalize=True) + rhs1.focus(rhs1.node(lp5.id)) + rhs1.command = "pull_out()" + + rhs2 = lhs.clone() + rhs2.pull_out(rhs2.node(lp5.id), position="next", normalize=True) + rhs2.focus(rhs2.node(lp5.id)) + rhs2.command = 'pull_out(position="next")' + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2], + } + + +class EgPullOut2(Example): + section = "Pull Out" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + lp4 = lhs.split(lp3, "x", ratio=0.33) + lp5 = lhs.split(lp4, "x") + lhs.focus(lp5) + + rhs1 = lhs.clone() + rhs1.pull_out(rhs1.node(lp5.id), normalize=True) + rhs1.focus(rhs1.node(lp5.id)) + rhs1.command = "pull_out()" + + return { + "lhs": lhs, + "rhs_items": [rhs1], + } + + +class EgPullOut3(Example): + section = "Pull Out" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + lp4 = lhs.tab(lp3, new_level=True) + lhs.focus(lp4) + + rhs1 = lhs.clone() + rhs1.pull_out(rhs1.node(lp4.id), normalize=True) + rhs1.focus(rhs1.node(lp4.id)) + rhs1.command = "pull_out()" + + return { + "lhs": lhs, + "rhs_items": [rhs1], + } + + +class EgPullOutToTab1(Example): + section = "Pull Out to Tab" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + lhs.focus(lp3) + + rhs1 = lhs.clone() + rhs1.pull_out_to_tab( + rhs1.node(lp3.id), + normalize=True, + ) + rhs1.command = "pull_out_to_tab()" + rhs1.focus(rhs1.node(lp3.id)) + + return { + "lhs": lhs, + "rhs_items": [rhs1], + } + + +class EgPullOutToTab2(Example): + section = "Pull Out to Tab" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + lp4 = lhs.tab(lp3, new_level=True) + lp5 = lhs.split(lp4, "x") + lhs.focus(lp5) + + rhs1 = lhs.clone() + rhs1.pull_out_to_tab( + rhs1.node(lp5.id), + normalize=True, + ) + rhs1.command = "pull_out_to_tab()" + rhs1.focus(rhs1.node(lp5.id)) + + return { + "lhs": lhs, + "rhs_items": [rhs1], + } + + +class EgPullOutToTab3(Example): + section = "Pull Out to Tab" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x") + lp3 = lhs.split(lp2, "y") + lp4 = lhs.tab(lp3, new_level=True) + lp5 = lhs.tab(lp4) + lhs.focus(lp5) + + rhs1 = lhs.clone() + rhs1.pull_out_to_tab( + rhs1.node(lp5.id), + normalize=True, + ) + rhs1.command = "pull_out_to_tab()" + rhs1.focus(rhs1.node(lp5.id)) + + return { + "lhs": lhs, + "rhs_items": [rhs1], + } + + +class EgAdvanced1(Example): + section = "Advanced Options for Branch Selection" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "x", ratio=0.33) + lp3 = lhs.split(lp2, "y", ratio=0.25) + lp4 = lhs.split(lp3.parent, "x") + _ = lhs.split(lp4, "y", ratio=0.6) + lhs.focus(lp3) + + rhs1 = lhs.clone() + rhs1.merge_with_neighbor_to_subtab( + rhs1.node(lp3.id), + "right", + src_selection="mru_deepest", + dest_selection="mru_deepest", + ) + rhs1.command = 'merge_to_subtab(\n "right",\n src_selection="mru_deepest",\n dest_selection="mru_deepest",\n)' + rhs1.focus(rhs1.node(lp3.id)) + + rhs2 = lhs.clone() + rhs2.merge_with_neighbor_to_subtab( + rhs2.node(lp3.id), + "right", + src_selection="mru_deepest", + dest_selection="mru_largest", + ) + rhs2.command = 'merge_to_subtab(\n "right",\n src_selection="mru_deepest",\n dest_selection="mru_largest",\n)' + rhs2.focus(rhs2.node(lp3.id)) + + rhs3 = lhs.clone() + rhs3.merge_with_neighbor_to_subtab( + rhs3.node(lp3.id), + "right", + src_selection="mru_largest", + dest_selection="mru_deepest", + ) + rhs3.command = 'merge_to_subtab(\n "right",\n src_selection="mru_largest",\n dest_selection="mru_deepest",\n)' + rhs3.focus(rhs3.node(lp3.id)) + + rhs4 = lhs.clone() + rhs4.merge_with_neighbor_to_subtab( + rhs4.node(lp3.id), + "right", + src_selection="mru_largest", + dest_selection="mru_largest", + ) + rhs4.command = 'merge_to_subtab(\n "right",\n src_selection="mru_largest",\n dest_selection="mru_largest",\n)' + rhs4.focus(rhs4.node(lp3.id)) + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2, rhs3, rhs4], + "subtitle": "Applicable to merge_to_subtab(), push_in(), pull_out()", + } + + +class EgAdvanced2(Example): + section = "Advanced Options for Branch Selection" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "y", ratio=0.25) + lp3 = lhs.split(lp2.parent, "x", ratio=0.33) + _ = lhs.split(lp3, "y") + lp5 = lhs.split(lp3.parent, "x") + lp6 = lhs.tab(lp5, new_level=True) + _ = lhs.split(lp6, "x") + lhs.focus(lp3) + + rhs1 = lhs.clone() + rhs1.merge_with_neighbor_to_subtab( + rhs1.node(lp3.id), + "right", + src_selection="mru_deepest", + dest_selection="mru_subtab_else_deepest", + ) + rhs1.focus(rhs1.node(lp3.id)) + rhs1.command = 'merge_to_subtab(\n "right",\n dest_selection="mru_subtab_else_deepest",\n)' + + rhs2 = lhs.clone() + rhs2.merge_with_neighbor_to_subtab( + rhs2.node(lp3.id), + "left", + src_selection="mru_deepest", + dest_selection="mru_subtab_else_deepest", + ) + rhs2.focus(rhs2.node(lp3.id)) + rhs2.command = 'merge_to_subtab(\n "left",\n dest_selection="mru_subtab_else_deepest",\n)' + + rhs3 = lhs.clone() + rhs3.merge_with_neighbor_to_subtab( + rhs3.node(lp3.id), + "left", + src_selection="mru_deepest", + dest_selection="mru_subtab_else_deepest", + ) + rhs3.focus(rhs2.node(lp3.id)) + rhs3.command = 'merge_to_subtab(\n "left",\n dest_selection="mru_subtab_else_deepest",\n)' + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2], + } + + +class EgAdvanced3(Example): + section = "Advanced Options for Branch Selection" + + def build_context_fragment(self): + lhs = make_tree() + lp1 = lhs.tab() + lp2 = lhs.split(lp1, "y", ratio=0.25) + lp3 = lhs.split(lp2.parent, "x", ratio=0.33) + _ = lhs.split(lp3, "y") + lp5 = lhs.split(lp3.parent, "x") + lp6 = lhs.split(lp5, "y", ratio=0.3) + _ = lhs.tab(lp6, new_level=True) + lhs.focus(lp3) + + rhs1 = lhs.clone() + rhs1.merge_with_neighbor_to_subtab( + rhs1.node(lp3.id), "right", dest_selection="mru_subtab_else_largest" + ) + rhs1.focus(rhs1.node(lp3.id)) + rhs1.command = 'merge_to_subtab(\n "right",\n dest_selection="mru_subtab_else_largest",\n)' + + rhs2 = lhs.clone() + rhs2.merge_with_neighbor_to_subtab( + rhs2.node(lp3.id), "left", dest_selection="mru_subtab_else_largest" + ) + rhs2.focus(rhs2.node(lp3.id)) + rhs2.command = 'merge_to_subtab(\n "left",\n dest_selection="mru_subtab_else_largest",\n)' + + return { + "lhs": lhs, + "rhs_items": [rhs1, rhs2], + } + + +def main(): + examples = [Eg() for Eg in _examples_registry] + + grouped_examples = collections.defaultdict(list) + for eg in examples: + grouped_examples[eg.section].append(eg.build_context()) + + index_tmpl = jinja_env.get_template(str(index_tmpl_path)) + index_html = index_tmpl.render({"sections": grouped_examples}) + + index_html_path.write_text(index_html) + + +if __name__ == "__main__": + main() diff --git a/static/visual_guide/index.css b/static/visual_guide/index.css new file mode 100644 index 0000000..a3a0017 --- /dev/null +++ b/static/visual_guide/index.css @@ -0,0 +1,140 @@ +body { + background: var(--bg_s); +} + +.guide { + margin: 5% 15%; + padding: 0; +} + +.main-header { + text-align: center; +} +.main-header h1 { + margin: 0.1rem; +} +.main-header .subtitle { + font-size: 1.4rem; + margin: 0; +} + +footer { + text-align: center; + font-size: 0.85rem; +} + + +.section, .section ul { + list-style: none; + margin: 0; + padding: 0; +} + + +.section .title { + text-align: center; +} +.section h2 { + margin: 0; +} +.section .subtitle { + text-align: center; + font-size: 0.8rem; + margin: 0.2rem; + margin-bottom: 2rem; +} + +.example { + display: grid; + place-items: center; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr; + grid-row-gap: 3rem; + margin: 3rem 1rem; +} + + +.tree { + +} + +.tree.rhs { + grid-column: 3; +} + +.command-label { + margin-bottom: 2rem; + font-size: 0.75rem; +} + +.example-advanced_1 .command-label { + font-size: 0.7rem; + margin-bottom: 1rem; + align-self: start; +} +.example-advanced_2 .command-label { + font-size: 0.7rem; + margin-top: 1rem; + align-self: start; +} +.example-advanced_3 .command-label { + font-size: 0.7rem; + margin-top: 1rem; + align-self: start; +} + +.node-tc { + +} + +.tc-bar { + box-sizing: border-box; + display: flex; + flex-direction: row; + border: 1px solid black; + border-bottom: 0; + font-size: 0.6rem; + background: var(--bg2); +} +.tc-bar-item { + box-sizing: border-box; + width: 1.7rem; + padding: 1px 3px; + border-right: 1px solid black; + display: flex; + justify-content: center; + align-items: center; +} +.tc-bar-item.active { + color: var(--red-dim); +} + +.node-t { + margin: 0; + padding: 0; +} + + +.node-sc { + display: flex; +} +.node-sc.sc-x { + flex-direction: row; +} +.node-sc.sc-y { + flex-direction: column; +} + +.node-p { + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid black; + font-size: 1.2rem; + + background: var(--bg_h); +} +.node-p.focused { + color: var(--red-dim); +} diff --git a/static/visual_guide/index.html b/static/visual_guide/index.html new file mode 100644 index 0000000..f474756 --- /dev/null +++ b/static/visual_guide/index.html @@ -0,0 +1,5194 @@ + + + + + +
+ + +Visual Guide
+spawn_split(program, "x")
spawn_split(program, "y")
spawn_tab(program)
spawn_tab(program, new_level=True)
spawn_tab(program)
spawn_tab(program, level=1)
merge_to_subtab("right")
merge_to_subtab("left")
merge_to_subtab("up")
merge_to_subtab("right")
push_in("right")
push_in("left")
pull_out()
pull_out(position="next")
pull_out()
pull_out()
pull_out_to_tab()
pull_out_to_tab()
pull_out_to_tab()
Applicable to merge_to_subtab(), push_in(), pull_out()+
merge_to_subtab( + "right", + src_selection="mru_deepest", + dest_selection="mru_deepest", +)
merge_to_subtab( + "right", + src_selection="mru_deepest", + dest_selection="mru_largest", +)
merge_to_subtab( + "right", + src_selection="mru_largest", + dest_selection="mru_deepest", +)
merge_to_subtab( + "right", + src_selection="mru_largest", + dest_selection="mru_largest", +)
merge_to_subtab( + "right", + dest_selection="mru_subtab_else_deepest", +)
merge_to_subtab( + "left", + dest_selection="mru_subtab_else_deepest", +)
merge_to_subtab( + "right", + dest_selection="mru_subtab_else_largest", +)
merge_to_subtab( + "left", + dest_selection="mru_subtab_else_largest", +)
{{ example.subtitle }}+
{{ rhs.command }}
Visual Guide
+