Replies: 3 comments 3 replies
-
I think when @willmcgugan addes a z-index support to CSS styles |
Beta Was this translation helpful? Give feedback.
-
I got something that seems to work by creating a widget that has a method to update size and overrides class Modal(Widget, can_focus=True):
has_focus: Reactive[bool] = Reactive(False)
mouse_over: Reactive[bool] = Reactive(False)
style: Reactive[str] = Reactive("")
height: Reactive[int | None] = Reactive(None)
def __init__(self, *, name: str | None = None, height: int | None = None) -> None:
super().__init__(name=name)
def _update_size(self, size: Size) -> None:
pass
def set_size(self, size: Size):
super()._update_size(size)
def __rich_repr__(self) -> rich.repr.Result:
yield "name", self.name
yield "has_focus", self.has_focus, False
yield "mouse_over", self.mouse_over, False
def render(self) -> RenderableType:
return Panel(
Align.center(
"modal content here",
),
title=self.__class__.__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) -> None:
self.has_focus = True
async def on_blur(self, event: events.Blur) -> None:
self.has_focus = 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
|
Beta Was this translation helpful? Give feedback.
-
I've got a something pretty much working - Extends the WindowView to allow setting z- on the internal Vertical Layout, and takes advantage of the VerticalLayout's Gutters to leave empty space alone... Edit - worth pointing out this currently doesn't adapt well to changing screen size - it does o k but not great. I find it good enough. YMMV class Modal(Widget):
content: Reactive[RenderableType] = Reactive('')
title: Reactive[str | None] = Reactive(None)
width: Reactive[int | None] = Reactive(None)
height: Reactive[int | None] = Reactive(None)
has_focus: Reactive[bool] = Reactive(False)
mouse_over: Reactive[bool] = Reactive(False)
style: Reactive[str] = Reactive("")
def __init__(
self, *,
content: RenderableType = 'Default Modal Content',
title: str | None = None,
width: int | None = None,
height: int | None = None,
name: str | None = None,
) -> None:
super().__init__(name=name)
self.content = content
self.title = title
self.width = width
self.height = height
self.view = None
def render(self):
console_size = self.console.options.size
if self.width:
width = min(self.width, self.console.options.max_width)
else:
width = int(.8 * console_size.width)
if self.height:
height = min(self.height, self.console.options.max_height)
else:
height = int(.8 * console_size.height)
offset_y = int((console_size.height - height) / 2)
offset_x = int((console_size.width - width) / 2)
if self.view:
gutter = (offset_y, 0, 0, offset_x)
self.view.layout.gutter = Spacing.unpack(gutter)
self.view.update(self)
self.border_style = 'green' if self.mouse_over else 'blue'
self.border = 'bold' if self.has_focus else 'round'
centered = Align.center(
self.content,
width=width,
height=height,
vertical='top',
)
return centered
async def on_focus(self, event: events.Focus) -> None:
self.has_focus = True
async def on_blur(self, event: events.Blur) -> None:
self.has_focus = 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
class ModalView(WindowView, layout=VerticalLayout):
def __init__(
self,
modal: Modal,
*,
z: int = 0,
auto_width: bool = False,
name: str | None = None
) -> None:
layout = VerticalLayout(auto_width=auto_width, z=z)
self.modal = self.widget = modal
layout.add(self.modal)
super(WindowView, self).__init__(name=name, layout=layout)
self.modal.view = self
def set_content(self, value):
self.modal.content = value
def get_arrangement(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]:
# Ignoring cache - would be better to figure out how to reliably load uncached when gutter has changed...
# Current problem seems to be gutter changes mid render of widget, but I'm not 100% sure on this
return list(self.layout.arrange(size, scroll))
def set_layout_size(self):
"""Set layout_size based on console.options.size"""
width = self.modal.width or int(self.console.options.size.width * .8)
render_width = int(((self.console.options.size.width - width) / 2) + width)
self.layout_size = render_width
async def on_mount(self):
self.set_layout_size()
async def on_resize(self, event: events.Resize) -> None:
self.set_layout_size()
await super().on_resize(event) To use, You'd instantiate a ModalView, passing a Modal into it, then dock the view - actually, here's some usage code, easier to show -> class ExampleApp(App):
modal_content_index: int = 1
async def on_load(self, event: events.Load) -> None:
await self.bind("b", "view.toggle('sidebar')", "Toggle Sidebar")
# Note 'm' hides and show's the modal
await self.bind("m", "view.toggle('modal')", "Toggle Modal")
# Note 'c' cycles content in the modal - was handy
await self.bind("c", "cycle_modal", "Cycle Modal Content")
await self.bind("q", "quit", "Quit")
async def action_cycle_modal(self):
"""Just showing some different content"""
self.modal_content_index += 1
match self.modal_content_index:
case 2:
content = Text.from_markup('[bold red]Stylized text[/] with [green on white]R$CH[/]')
case 3:
content = Table('Entry', 'Album')
content.add_row('Pulk/Pull Revolving Doors', 'Amnesiac')
content.add_row('Paranoid Android', 'OK Computer')
case 4:
content = Markdown(
"# Foobar\nFoobar is a Python library for dealing with word pluralization.\n\n## Installation\n"
"Use the package manager [pip](https://pip.pypa.io/en/stable/) to install foobar.\n\n```bash\n"
)
case _:
self.modal_content_index = 1
content = 'Just a string.'
self.modal.set_content(content)
async def on_mount(self, event: events.Mount) -> None:
""" Everything up top is just app setup, not important to the floating Modal bit - left it in as it's how I tested while dev'ing and makes a decently dense backdrop"""
body = ScrollView(
'\n'.join('Fusce rhoncus bibendum est, mattis porta dolor imperdiet eget' for _ in range(50)), gutter=1,
)
await self.view.dock(Header(), edge="top")
await self.view.dock(Footer(), edge="bottom")
menu = Table.grid()
for item in random.choices(dir(__builtins__), k=20):
menu.add_row(f'◉ {item}')
await self.view.dock(ScrollView(menu), edge="left", size=30, name="sidebar")
await self.view.dock(body, edge="right")
""" !!! This is the important bit - Create the ModalView and dock it """
self.modal = ModalView(
Modal(content='Just some modal content', width=35, height=15, title='Modal Title'),
z=1,
)
await self.view.dock(self.modal, edge='left', z=1, name='modal')
if __name__ == '__main__':
ExampleApp.run(title="Modal Example App", log='do_some_modal.log') Currently expects you to set the width and height of the modal, else the defaults are pretty basic (80% of the screen, no wrapping to the height/width of content) but that flexible sizing should be possible to code up, especially if you keep the assumption that there will be one modal and nothing else at whatever z-index you render your modal. That assumption makes a lot of things simpler, I'd recommend it. |
Beta Was this translation helpful? Give feedback.
-
Is it possible to have "modals" or floating widgets which would appear to display over other widgets? I've gotten kind of close with the dock view and a widget which is left-docked but has the x and y offsets updated. This strangely results in the entire area under the widget being blocked out though:
![image](https://user-images.githubusercontent.com/1845299/153824058-7762c8a1-ddbf-4dbb-a900-e5f67949e34f.png)
Beta Was this translation helpful? Give feedback.
All reactions