Skip to content

Instantly share code, notes, and snippets.

@ddkasa
Last active November 18, 2024 09:13
Show Gist options
  • Save ddkasa/4c22a9ddff79f731732c8b91cf60021c to your computer and use it in GitHub Desktop.
Save ddkasa/4c22a9ddff79f731732c8b91cf60021c to your computer and use it in GitHub Desktop.
Textual Bug Report MRE - Offset Widgets Popping In And Out While Scrolling
from __future__ import annotations
from dataclasses import dataclass
from textual import on
from textual.app import App, ComposeResult
from textual.containers import ScrollableContainer
from textual.events import MouseDown, MouseEvent, MouseMove, MouseUp, Resize
from textual.geometry import Offset
from textual.message import Message
from textual.widget import Widget
from textual.widgets import Label
class Adjustable(Widget):
@dataclass
class Resize(Message):
adjustable: Adjustable
delta: int
def __init__(self, id: str) -> None:
super().__init__(id=id)
self.clicked: Offset | None = None
self.styles.width = 10
def compose(self) -> ComposeResult:
yield Label("Test Content")
async def on_mouse_down(self, event: MouseDown) -> None:
if self.app.mouse_captured is None:
self.capture_mouse()
await self.is_focused(event)
async def on_mouse_up(self, event: MouseUp) -> None:
if self.app.mouse_captured:
self.capture_mouse(False)
await self.is_unfocused()
async def is_focused(self, event: MouseEvent) -> None:
self.clicked = event.offset
self.moving = 2 < event.offset.x < (self.size.width - 2)
async def is_unfocused(self) -> None:
self.clicked = None
async def on_mouse_move(self, event: MouseMove) -> None:
if self.clicked is not None and event.button != 0:
if hasattr(self, "moving") and self.moving:
await self._move(event)
else:
await self._resize(event)
async def _resize(self, event: MouseMove) -> None:
delta = event.delta_x
if self.clicked and self.clicked.x < 2:
self.offset += Offset(delta, 0)
delta *= -1
self.styles.width = self.styles.width.value + delta
self.refresh()
self.post_message(Adjustable.Resize(self, delta))
async def _move(self, event: MouseMove) -> None:
if event.delta:
self.offset = self.offset + Offset(event.delta.x)
class Timeline(Widget):
def __init__(self) -> None:
super().__init__()
self.styles.width = 5760
self.adjustables = (
Adjustable(id="adj-1"),
Adjustable(id="adj-2"),
Adjustable(id="adj-3"),
)
def compose(self) -> ComposeResult:
yield from self.adjustables
@on(Adjustable.Resize)
def _resize_compensation(self, message: Adjustable.Resize) -> None:
offset = Offset(message.delta)
for i in range(1, len(self.adjustables) + 1):
adj = self.adjustables[-i]
if adj.id == message.adjustable.id:
break
with adj.prevent(Adjustable.Resize, Resize):
adj.offset = adj.offset - offset
class BugReportApp(App):
CSS = """
Adjustable {
background: $panel;
align-vertical: middle;
height: 100%;
min-width: 6;
border: outer $secondary;
}
ScrollableContainer {
height: auto;
Timeline {
background: $panel-darken-1;
width: 100%;
height: 100%;
layout: horizontal;
height: 13;
}
}
"""
def compose(self) -> ComposeResult:
with ScrollableContainer():
yield Timeline()
if __name__ == "__main__":
app = BugReportApp()
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment