Exemplo n.º 1
0
class NodeTree(TreeControl):
    nodenums = Reactive("")
    nnodes = Reactive(0)

    def __init__(self, label=None, *args):
        super().__init__(label, *args)
        self.nnodes = 0
        self.original_label = label
        self.root.tree.guide_style = "green4"
        self._tree.style = Style(color="deep_sky_blue1")

    def __reinit__(self):
        # reinitialize tree for new nodes
        self._tree.label = self.root
        self.data = {}
        self.id = NodeID(0)
        self.nodes: dict[NodeID, TreeNode[NodeDataType]] = {}
        self._tree = Tree(self.original_label)
        self.root: TreeNode[NodeDataType] = TreeNode(None, self.id, self,
                                                     self._tree,
                                                     self.original_label, {})
        self._tree.label = self.root
        self.nodes[NodeID(self.id)] = self.root
        self.root.tree.guide_style = "blue"
        self._tree.style = Style(color="deep_sky_blue1")

    async def handle_tree_click(self, message: TreeClick[dict]):
        """Called in response to a tree click."""
        new_active_node = message.node.data.get("node_id")
        # convert active node string to bytes
        bs.net.actnode(new_active_node)

    async def watch_nnodes(self, nnodes):

        # reinitialize if nodes exist
        if list(self.nodes.keys())[1:]:
            self.__reinit__()

        # reload nodes
        for node in self.nodenums:
            node_id = self.node_ids[self.nodenums.index(node)]
            await self.add(self.root.id, node, {
                "node_num": node,
                "node_id": node_id
            })
        await self.root.expand()

    def set_nodedict(self, nodedict):

        self.nodenums = []
        self.node_ids = []
        for node_id, node in nodedict.items():
            nodenum, node_id = node['num'], node_id
            self.nodenums.append(f'Node {nodenum}')
            self.node_ids.append(node_id)
        self.nnodes = len(nodedict)
Exemplo n.º 2
0
class TextBox(Widget, FocusMixin):
    """textbox

    to subscribe to events, simply add `on_<name>_change` function to your widget/app

    e.g.:
    def __init__(self):
        self.tb = TextBox('hello_world')

    def on_hello_world_change(self, event):
        self.log(event.value)
    """
    value: Reactive[str] = Reactive('')
    title: Reactive[str] = Reactive('')

    def __init__(self, name: str, title=''):
        super().__init__(name)
        self.title = title
        self.name = name
        self._height = 3
        self._text_change_event = ValueEvent.make_new(f'{self.name}_change', bound=type(self))

    def render(self):
        return Panel(
            Align.left(self._display_str),
            title=self.title,
            border_style='blue',
            box=box.DOUBLE if self.has_focus else box.ROUNDED,
            # style=None,
            height=self._height,
        )

    @property
    def _display_str(self):
        return self.value

    async def on_key(self, event):
        k = event.key
        changed = True
        if k in {'delete', 'ctrl+h'}:
            self.value = self.value[:-1]
        elif k == 'escape':
            self.value = ''
        elif k in valid_chars:
            self.value += k
        else:
            changed = False

        if changed:
            await self.post_message(self._text_change_event(value=self.value))
Exemplo n.º 3
0
class TextBoxOrig(Widget, FocusMixin):
    """base text box. replaced by the new `TextBox`"""

    style: Reactive[str] = Reactive('')
    data: Reactive[str] = Reactive('')
    height: Reactive[int | None] = Reactive(3)
    width: Reactive[int | None] = Reactive(None)
    title: Reactive[str] = Reactive('')

    def __init__(self, title='textbox', width=40, height=3):
        super().__init__()
        self.width = width
        self.height = height
        self.title = title
        self._cbs = []

    async def on_key(self, event):
        k = event.key
        if k in {'delete', 'ctrl+h'}:
            self.data = self.data[:-1]
        elif k in valid_chars:
            self.data += k
        else:
            return False
        return True

    @property
    def _display_str(self):
        return self.data[-self.width:]

    def render(self):
        return Panel(
            Align.left(self._display_str, vertical='top'),
            title=self.title,
            border_style='blue',
            box=box.HEAVY if self.has_focus else box.ROUNDED,
            style=self.style,
            height=self.height,
            width=self.width,
        )

    def register(self, *cb: Callable[[str], None]):
        """register callbacks to be alerted when data is updated"""
        self._cbs.extend(cb)

    def watch_data(self, value: str) -> None:
        """update registered callbacks"""
        for cb in self._cbs:
            cb(value)
Exemplo n.º 4
0
class SmoothApp(App):
    """Demonstrates smooth animation. Press 'b' to see it in action."""
    async def on_load(self) -> None:
        """Bind keys here."""
        await self.bind("b", "toggle_sidebar", "Toggle sidebar")
        await self.bind("q", "quit", "Quit")

    show_bar = Reactive(False)

    def watch_show_bar(self, show_bar: bool) -> None:
        """Called when show_bar changes."""
        self.bar.animate("layout_offset_x", 0 if show_bar else -40)

    def action_toggle_sidebar(self) -> None:
        """Called when user hits 'b' key."""
        self.show_bar = not self.show_bar

    async def on_mount(self) -> None:
        """Build layout here."""
        footer = Footer()
        self.bar = Placeholder(name="left")

        await self.view.dock(footer, edge="bottom")
        await self.view.dock(Placeholder(), Placeholder(), edge="top")
        await self.view.dock(self.bar, edge="left", size=40, z=1)

        self.bar.layout_offset_x = -40
Exemplo n.º 5
0
class Traffic(Widget):
    table: Union[Reactive[Table], Table] = Reactive(Table())

    def __init__(self, name=None):
        super().__init__(name)
        self.table = Table()

    def render(self) -> RenderableType:
        return self.table

    def set_traffic(self, trafict: dict, active_node_num: float):
        # add rows to table
        self.table = Table(
            title=f'[bold blue]Traffic for node # {active_node_num}',
            box=box.MINIMAL_DOUBLE_HEAD)

        # add the columns
        for col in trafict.keys():
            self.table.add_column(col, header_style=Style(color="magenta"))

        ntraf = len(trafict['id'])

        if ntraf > 0:
            for i in range(ntraf):
                # limit traffic to 100 aircraft on screen
                if i > 100:
                    break
                row = []
                for col in trafict.keys():
                    table_value = trafict[col][i]
                    row.append(table_value)
                self.table.add_row(*row,
                                   style=Style(color='bright_green',
                                               bgcolor="bright_black"))
Exemplo n.º 6
0
class FeatureList(View, layout=VerticalLayout, can_focus=True):
    name = 'feature list'
    selection = Reactive(None)

    def __init__(self, feature_set: CompiledFeatureSet) -> None:
        super().__init__()
        self.feature_set = feature_set
        self.selection = None

        self.features = []
        for i, feature in enumerate(self.feature_set.features):
            name = feature.name
            if not feature.take_value:
                rely_on = self.feature_set.get_dependencies(name)
                if rely_on:
                    rely_on = " rely on \\[" + ', '.join(rely_on) + ']'
                else:
                    rely_on = ""
                wg = CheckboxItem(
                    self,
                    key=feature.name,
                    explain=feature.description + rely_on,
                    enabled=self.feature_set.values.get(name),
                    selected=False,
                )
                self.features.append(wg)
                self.layout.add(wg)

    @property
    def features_num(self):
        return len(self.features)

    def handle_button_pressed(self, message: ButtonPressed) -> None:
        self.feature_set.set(message.sender.key, not message.sender.enabled)
        for feature in self.features:
            feature.enabled = self.feature_set.values[feature.key]

    def on_key(self, event: events.Key):
        if self.selection is None:
            if event.key == events.Keys.Up:
                self.selection = self.features_num - 1
            elif event.key == events.Keys.Down:
                self.selection = 0
            if event.key == events.Keys.Up or event.key == events.Keys.Down:
                self.features[self.selection].selected = True

        elif event.key == events.Keys.Up or event.key == events.Keys.Down:
            self.features[self.selection].selected = False
            if event.key == events.Keys.Up:
                self.selection = (self.selection + self.features_num -
                                  1) % self.features_num

            if event.key == events.Keys.Down:
                self.selection = (self.selection + 1) % self.features_num
            self.features[self.selection].selected = True
        elif event.key == events.Keys.Enter or event.key == " ":
            self.handle_button_pressed(
                ButtonPressed(self.features[self.selection]))
Exemplo n.º 7
0
class ToTrack(Widget):
    clicker: Reactive[Clickable] = Reactive(None)
    button: Reactive[ResetButton] = Reactive(None)

    def __init__(self, name: str | None = None, color='dark_green') -> None:
        super().__init__(name)
        self.clicker = Clickable(name)
        self.color = color
        self.button = ResetButton()

    def render(self):
        # g = Group(self.clicker, self.button)
        # return Panel(
        #     g,
        #     style=Style(bgcolor='white'),
        # )
        st = Style(bgcolor=self.color)
        grid = Table(show_header=False,
                     box=box.ROUNDED,
                     show_lines=False,
                     expand=True,
                     title=self.name,
                     style=st)
        grid.add_row(self.clicker, style=st)
        grid.add_row(self.button, style=st)
        return grid

    def on_click(self, event: events.Click) -> None:
        self.log(f'I HAVE CLICKED: {self._refresh_button_clicked(event)}')
        if self._refresh_button_clicked(event):
            self.clicker.reset()
        else:
            self.clicker.count += 1

        self.refresh()

    def _refresh_button_clicked(self, event: events.Click):
        x, y = self.size
        self.log('>>>>>>>', event.x, event.y, x, y)

        return event.x in {35, 36} and event.y == 17
Exemplo n.º 8
0
class Numbers(Widget):
    """The digital display of the calculator."""

    value = Reactive("0")

    def render(self) -> RenderableType:
        """Build a Rich renderable to render the calculator display."""
        return Padding(
            Align.right(FigletText(self.value), vertical="middle"),
            (0, 1),
            style="white on rgb(51,51,51)",
        )
Exemplo n.º 9
0
class Echobox(ScrollView):
    text = Reactive("")

    async def watch_text(self, text):
        await self.update(
            Panel(Text(text),
                  height=max(8, 2 + text.count('\n')),
                  box=box.SIMPLE,
                  style=Style(bgcolor="grey53")))

    def set_text(self, text: str):
        self.text = text
Exemplo n.º 10
0
class MouseOverMixin:
    _mouse_over: Reactive[bool] = Reactive(False)

    async def on_enter(self, event: events.Enter) -> None:
        self._mouse_over = True

    async def on_leave(self, event: events.Leave) -> None:
        self._mouse_over = False

    @property
    def mouse_over(self):
        return self._mouse_over
Exemplo n.º 11
0
class NodeInfo(Widget):
    table: Union[Reactive[Table], Table] = Reactive(Table())

    def __init__(self, name=None):
        super().__init__(name)
        self.table = Table()

        self.regular_style = Style(color='bright_green',
                                   bgcolor="bright_black")
        self.actnode_style = Style(color='bright_yellow', bgcolor="black")

    def render(self) -> RenderableType:
        return self.table

    def set_nodes(self, nodedict: dict):
        # add rows to table
        self.table = Table(title='[bold blue]Nodes',
                           box=box.MINIMAL_DOUBLE_HEAD)
        self.table.add_column('Node #', header_style=Style(color="magenta"))
        self.table.add_column('Scenario', header_style=Style(color="magenta"))
        self.table.add_column('Time', header_style=Style(color="magenta"))
        self.table.add_column('Aircraft', header_style=Style(color="magenta"))
        self.table.add_column('Current conflicts',
                              header_style=Style(color="magenta"))
        self.table.add_column('Total conflicts',
                              header_style=Style(color="magenta"))
        self.table.add_column('Current LOS',
                              header_style=Style(color="magenta"))
        self.table.add_column('Total LOS', header_style=Style(color="magenta"))

        for node_id, node in nodedict.items():

            if bs.net.act == node_id:
                self.table.add_row(node['num'],
                                   node['scenename'],
                                   node['time'],
                                   f'{node["nair"]}',
                                   f'{node["nconf_cur"]}',
                                   f'{node["nconf_tot"]}',
                                   f'{node["nlos_cur"]}',
                                   f'{node["nlos_tot"]}',
                                   style=self.actnode_style)
            else:
                self.table.add_row(node['num'],
                                   node['scenename'],
                                   node['time'],
                                   f'{node["nair"]}',
                                   f'{node["nconf_cur"]}',
                                   f'{node["nconf_tot"]}',
                                   f'{node["nlos_cur"]}',
                                   f'{node["nlos_tot"]}',
                                   style=self.regular_style)
Exemplo n.º 12
0
class Textline(Widget):
    text = Reactive("")
    style = Style(bgcolor="grey37")

    def __init__(self, text="", name=None):
        super().__init__(name)
        self.text = text

    def render(self) -> RenderableType:
        return self.text

    def set_text(self, text):
        self.text = text
Exemplo n.º 13
0
class Hover(Widget):

    mouse_over = Reactive(False)

    def render(self) -> Panel:
        return Panel("Hello [b]World[/b]",
                     style=("on red" if self.mouse_over else ""))

    def on_enter(self) -> None:
        self.mouse_over = True

    def on_leave(self) -> None:
        self.mouse_over = False
Exemplo n.º 14
0
class EchoInfo(Widget, can_focus=True):

    has_focus = Reactive(False)
    mouse_over = Reactive(False)
    style = Reactive("")
    height = Reactive(None)

    def __init__(self, name=None, height=None):
        super().__init__(name=name)
        self.height = height
        self.pretty_text = Text('BlueSky   Console Client',
                                style=Style(color='blue', bold=True),
                                justify='center')

    def __rich_repr__(self) -> rich.repr.Result:
        yield "name", self.name

    def render(self) -> RenderableType:
        return Panel(
            Align.center(self.pretty_text, vertical="middle"),
            # title=self.name,
            border_style="green" if self.mouse_over else "blue",
            box=box.HEAVY if self.has_focus else box.ROUNDED,
            style=self.style,
            height=self.height,
        )

    async def on_focus(self, event: events.Focus):
        self.has_focus = True

    async def on_blur(self, event: events.Blur):
        self.has_focus = False

    async def on_enter(self, event: events.Enter):
        self.mouse_over = True

    async def on_leave(self, event: events.Leave):
        self.mouse_over = False
Exemplo n.º 15
0
class FocusMixin:
    _has_focus: Reactive[bool] = Reactive(False)

    async def on_focus(self, event: events.Focus) -> None:
        self.log('>>>>>> HAS FOCUS NOW')
        self._has_focus = True

    async def on_blur(self, event: events.Blur) -> None:
        self.log('>>>>>> BLURRED NOW')
        self._has_focus = False

    @property
    def has_focus(self):
        return self._has_focus
Exemplo n.º 16
0
class CheckboxItem(Widget):
    enabled = Reactive(False)
    selected = Reactive(False)

    def __init__(self,
                 parent: Widget,
                 *,
                 key: str,
                 explain: str,
                 enabled: bool,
                 selected: bool = False) -> None:
        super().__init__()
        self._parent = parent
        self.key = key
        self.explain = explain
        self.enabled = enabled
        self.selected = selected

    def __rich_repr__(self) -> rich.repr.Result:
        yield "enabled", self.enabled
        yield "label", self.key

    def render(self) -> Table:
        grid = Table.grid(padding=1)
        grid.add_column()
        grid.add_row(
            "\\[x]" if self.enabled else "[ ]",
            self.key,
            self.explain,
            style=Style(reverse=self.selected),
        )

        return grid

    async def on_click(self, event: events.Click) -> None:
        event.prevent_default().stop()
        await self.emit(ButtonPressed(self))
Exemplo n.º 17
0
class Clickable(Widget):
    """click tally"""
    count: Reactive[int] = Reactive(95)
    title: Reactive[str] = Reactive('')

    def __init__(self, name):
        super().__init__(name)
        self.title = name
        self._button = ResetButton()

    def render(self):
        return Align.center(num_to_str(self.count),
                            vertical='middle',
                            height=15)

    # def __rich_console__(self, console: Console, options: ConsoleOptions):
    #     yield Align.center(num_to_str(self.count), vertical='middle')

    def reset(self):
        self.log('RESETTING')
        self.count = 0

    async def on_click(self, event: events.Click) -> None:
        self.count += 1
Exemplo n.º 18
0
class CheckBox(Button):
    checked: Reactive[bool] = Reactive(False)

    def __init__(self, label, name: str = '', check_style: CheckStyle = None):
        name = name or label
        super().__init__(label, name or label)
        self.checked = False
        self.check_style = check_style or CheckStyle.random()
        self._event = ValueEvent.make_new(f'{self.name}_change', bound=type(self))

    def render(self):
        t = Table('check', 'label', show_header=False, border_style=None, box=None)
        t.add_row(self.check_style[self.checked], self.label)
        return t

    async def on_click(self, event: events.Click) -> None:
        self.checked = not self.checked
        await self.post_message(self._event(self.checked))
Exemplo n.º 19
0
class Tile(Widget):
    mouse_over = Reactive(False)

    def __init__(self, name: str | None = None, num: int | None = None) -> None:
        super().__init__(name)
        self._text: str = ""
        self._num = num

    def render(self) -> Panel:
        return Panel(
            Align.center(FigletText(self._text), vertical="middle"),
            style=("on red" if self.mouse_over else ""),
        )

    async def on_enter(self) -> None:
        self.mouse_over = True

    async def on_leave(self) -> None:
        self.mouse_over = False

    async def on_click(self, event: events.Click) -> None:
        await self.app.make_turn(self._num)
Exemplo n.º 20
0
class ConsoleUI(App):
    cmdtext: Union[Reactive[str], str] = Reactive("")
    echotext: Union[Reactive[str], str] = Reactive("")
    infotext: Union[Reactive[str], str] = Reactive("")
    nodedict = Reactive(dict())
    nodetimes = Reactive("0")
    trafsimt = Reactive("")
    ntraf = Reactive(0)
    nnodes = Reactive(0)
    active_node_num = Reactive(0)

    cmdbox: Textline
    echobox: Echobox
    infoline: Textline
    echoinfo: EchoInfo
    nodeinfo: NodeInfo
    traffic: Traffic
    tree: NodeTree
    instance: App

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        ConsoleUI.instance = self

    def echo(self, text, flags=None):
        self.echotext = text + '\n' + self.echotext

    def set_infoline(self, text):
        self.infotext = text

    def set_nodes(self, nodes, nodetimes):
        self.nodedict = nodes
        self.nnodes = len(nodes)
        self.nodetimes = nodetimes

    def set_traffic(self, gen_data, traffic, active_node):
        self.trafdict = traffic
        self.trafsimt = str(gen_data['simt'])
        self.ntraf = len(traffic['id'])
        self.active_node_num = self.nodedict[active_node][
            'num'] if self.nodedict else 1

    async def on_key(self, key: events.Key):
        if key.key == Keys.ControlH:
            self.cmdtext = self.cmdtext[:-1]
        elif key.key == Keys.Delete:
            self.cmdtext = ""
        elif key.key == Keys.Enter:
            self.echotext = self.cmdtext + '\n' + self.echotext
            bs.stack.stack(self.cmdtext)
            self.cmdtext = ""
        elif key.key == Keys.Escape:
            await self.action_quit()
        elif key.key in string.printable:
            self.cmdtext += key.key

    async def watch_infotext(self, infotext):
        self.infoline.set_text(f"[black]Current node:[/black] {infotext}")

    async def watch_cmdtext(self, cmdtext):
        self.cmdbox.set_text(f"[blue]>>[/blue] {cmdtext}")

    async def watch_nodetimes(self, nodetimes):
        self.nodeinfo.set_nodes(self.nodedict)
        self.tree.set_nodedict(self.nodedict)

    async def watch_nnodes(self, nnodes):
        await self.nodebody.update(self.nodeinfo)

    async def watch_echotext(self, echotext):
        self.echobox.set_text(echotext)

    async def watch_ntraf(self, ntraf):
        await self.trafbody.update(self.traffic)

    async def watch_trafsimt(self, trafsimt):
        self.traffic.set_traffic(self.trafdict, self.active_node_num)

    async def watch_active_node_num(self, active_node_num):
        self.nodeinfo.set_nodes(self.nodedict)

    async def on_mount(self, event: events.Mount):
        self.cmdbox = Textline("[blue]>>[/blue]")
        self.echobox = Echobox(
            Panel(Text(),
                  height=8,
                  box=box.SIMPLE,
                  style=Style(bgcolor="grey53")))
        self.infoline = Textline("[black]Current node: [/black]")
        self.echoinfo = EchoInfo("BlueSky")
        self.nodeinfo = NodeInfo(name="nodeinfo")
        self.traffic = Traffic(name="traffic")
        self.tree = NodeTree("Switch Nodes", {})

        await self.bind(Keys.Escape, "quit", "Quit")
        await self.bind(Keys.ControlT, "view.toggle('trafficbody')",
                        "Show traffic")
        await self.bind(Keys.ControlB, "view.toggle('nodedock')", "Show batch")

        await self.view.dock(Footer(), edge="bottom", size=1)
        await self.view.dock(self.cmdbox, edge="bottom", size=1)

        echorow = DockView()
        await echorow.dock(self.echoinfo, edge="right", size=20)
        await echorow.dock(self.echobox, edge="left")

        await self.view.dock(echorow, edge="bottom", size=8)
        await self.view.dock(self.infoline, edge="bottom", size=1)

        self.trafbody = ScrollView(self.traffic, name="trafficbody")
        await self.view.dock(self.trafbody, edge='top')

        nodedock = DockView(name='nodedock')
        self.nodebody = ScrollView(self.nodeinfo, name="nodeinfobody")
        await nodedock.dock(self.nodebody, edge='left', size=90)

        # Add the node tree to a scroll view
        self.treebody = ScrollView(self.tree)
        await nodedock.dock(self.treebody)
        await self.view.dock(nodedock, edge="right")

        await self.set_focus(self.cmdbox)

        self.set_interval(0.2, bs.net.update, name='Network')
Exemplo n.º 21
0
class EmojiResults(Widget, FocusMixin):
    """filtered panel of emoji"""

    width: Reactive[int | None] = Reactive(None)
    data: Reactive[str] = Reactive('')
    style: Reactive[str] = Reactive('')
    offset: Reactive[int] = Reactive(0)

    def __init__(self, width=50):
        super().__init__()
        self._emoji = []
        self._title_override = ''
        self.border_style = 'red'
        self.width = width
        self._orig_border_style = self.border_style

        # keeps border updating functions from overlapping
        self._last_copied_cxl_ref = [False]

    @property
    def _title_str(self):
        return self._title_override or f'emoji results ({self._num_matching})'

    @property
    def _num_matching(self):
        return sum(self.data.lower() in s.lower() for s in _emoji)

    @property
    def height(self):
        return self.size.height

    def render(self):
        return Panel(
            Align.left(self._emoji_str),
            title=self._title_str,
            border_style=self.border_style,
            style=self.style,
            height=min(self.height,
                       len(self._emoji) + 2),
            width=self.width,
        )

    @property
    def _emoji_str(self) -> str:
        self._emoji = self._get_emoji(self.data,
                                      limit=self.height,
                                      offset=self.offset)
        return '\n'.join(s[:self.width - 10] for s in self._emoji)

    async def on_key(self, event):
        k = event.key
        if k == 'down':
            self.down()
        elif k == 'up':
            self.up()
        else:
            self.offset = 0

    async def on_mouse_scroll_up(self, event):
        self.down(3)

    async def on_mouse_scroll_down(self, event):
        self.up(3)

    async def on_click(self, event: events.Click) -> None:
        """copy emoji to clipboard"""
        info = val = None
        try:
            idx = event.y - 1
            if idx < self.height - 2:
                # subtract 2 for the borders
                val = self._emoji[idx].split()[0]
                pyperclip.copy(val)
                info = f'copied {val} to clipboard'
                border_style = 'green'
        except IndexError:
            pass
        except Exception:
            val = 'error'
            info = 'unable to copy'
            border_style = 'purple'

        if info:
            self._alert_copied(info, border_style)  # noqa
        self.log(f'bleep {val}')

    def _alert_copied(self, info, border_style):
        """change border/title to reflect that copying has occurred"""
        self._title_override = info
        self.border_style = border_style
        self.refresh()

        self._last_copied_cxl_ref[0] = True
        self._last_copied_cxl_ref = canceled = [False]

        def _reset_title():
            if canceled[0]:
                return

            self._title_override = ''
            self.border_style = self._orig_border_style
            self.refresh()

        self.set_timer(1, _reset_title)

    def down(self, amount=1):
        if len(self._emoji) > 1:
            self.offset += amount

    def up(self, amount=1):
        self.offset -= amount

    def validate_offset(self, v):
        return max(v, 0)

    @staticmethod
    def _get_emoji(s='', limit=0, offset=0):
        s = s.lower()
        limited = list
        if limit > 0:
            limited = partial(take, limit)
        emoji = (f'{e} {name}' for name, e in _emoji.items()
                 if s in name.lower())
        take(offset, emoji)
        return limited(emoji)
Exemplo n.º 22
0
class Calculator(GridView):
    """A working calculator app."""

    DARK = "white on rgb(51,51,51)"
    LIGHT = "black on rgb(165,165,165)"
    YELLOW = "white on rgb(255,159,7)"

    BUTTON_STYLES = {
        "AC": LIGHT,
        "C": LIGHT,
        "+/-": LIGHT,
        "%": LIGHT,
        "/": YELLOW,
        "X": YELLOW,
        "-": YELLOW,
        "+": YELLOW,
        "=": YELLOW,
    }

    display = Reactive("0")
    show_ac = Reactive(True)

    def watch_display(self, value: str) -> None:
        """Called when self.display is modified."""
        # self.numbers is a widget that displays the calculator result
        # Setting the attribute value changes the display
        # This allows us to write self.display = "100" to update the display
        self.numbers.value = value

    def compute_show_ac(self) -> bool:
        """Compute show_ac reactive value."""
        # Condition to show AC button over C
        return self.value in ("", "0") and self.display == "0"

    def watch_show_ac(self, show_ac: bool) -> None:
        """When the show_ac attribute change we need to update the buttons."""
        # Show AC and hide C or vice versa
        self.c.visible = not show_ac
        self.ac.visible = show_ac

    def on_mount(self) -> None:
        """Event when widget is first mounted (added to a parent view)."""

        # Attributes to store the current calculation
        self.left = Decimal("0")
        self.right = Decimal("0")
        self.value = ""
        self.operator = "+"

        # The calculator display
        self.numbers = Numbers()
        self.numbers.style_border = "bold"

        def make_button(text: str, style: str) -> Button:
            """Create a button with the given Figlet label."""
            return Button(FigletText(text), style=style, name=text)

        # Make all the buttons
        self.buttons = {
            name: make_button(name, self.BUTTON_STYLES.get(name, self.DARK))
            for name in "+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",")
        }

        # Buttons that have to be treated specially
        self.zero = make_button("0", self.DARK)
        self.ac = make_button("AC", self.LIGHT)
        self.c = make_button("C", self.LIGHT)
        self.c.visible = False

        # Set basic grid settings
        self.grid.set_gap(2, 1)
        self.grid.set_gutter(1)
        self.grid.set_align("center", "center")

        # Create rows / columns / areas
        self.grid.add_column("col", max_size=30, repeat=4)
        self.grid.add_row("numbers", max_size=15)
        self.grid.add_row("row", max_size=15, repeat=5)
        self.grid.add_areas(
            clear="col1,row1",
            numbers="col1-start|col4-end,numbers",
            zero="col1-start|col2-end,row5",
        )
        # Place out widgets in to the layout
        self.grid.place(clear=self.c)
        self.grid.place(*self.buttons.values(),
                        clear=self.ac,
                        numbers=self.numbers,
                        zero=self.zero)

    def handle_button_pressed(self, message: ButtonPressed) -> None:
        """A message sent by the button widget"""

        assert isinstance(message.sender, Button)
        button_name = message.sender.name

        def do_math() -> None:
            """Does the math: LEFT OPERATOR RIGHT"""
            self.log(self.left, self.operator, self.right)
            try:
                if self.operator == "+":
                    self.left += self.right
                elif self.operator == "-":
                    self.left -= self.right
                elif self.operator == "/":
                    self.left /= self.right
                elif self.operator == "X":
                    self.left *= self.right
                self.display = str(self.left)
                self.value = ""
                self.log("=", self.left)
            except Exception:
                self.display = "Error"

        if button_name.isdigit():
            self.display = self.value = self.value.lstrip("0") + button_name
        elif button_name == "+/-":
            self.display = self.value = str(Decimal(self.value or "0") * -1)
        elif button_name == "%":
            self.display = self.value = str(
                Decimal(self.value or "0") / Decimal(100))
        elif button_name == ".":
            if "." not in self.value:
                self.display = self.value = (self.value or "0") + "."
        elif button_name == "AC":
            self.value = ""
            self.left = self.right = Decimal(0)
            self.operator = "+"
            self.display = "0"
        elif button_name == "C":
            self.value = ""
            self.display = "0"
        elif button_name in ("+", "-", "/", "X"):
            self.right = Decimal(self.value or "0")
            do_math()
            self.operator = button_name
        elif button_name == "=":
            if self.value:
                self.right = Decimal(self.value)
            do_math()