def __init__(self, parent: AppWidget): super().__init__(parent, "Time") d = TimeWidget(self, Config.DateTime.date_format(), h_alignment=TextWidget.HAlignment.CENTER) d.rectangle = Rectangle(AnchoredPoint(0, 0, Anchor.TOP_LEFT), self.size) d.fit_font_size() d.set_height(d.preferred_size.height, VAnchor.TOP) t = TimeWidget(self, Config.DateTime.time_format()) t.rectangle = Rectangle( d.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT) + Point(0, 2), self.size) t.fit_font_size() t.set_height(t.preferred_size.height, VAnchor.TOP) h = self.height - t.bottom s = SplitSecondSpinner(self) s.rectangle = Rectangle( t.position(Anchor.BOTTOM_RIGHT).anchored(Anchor.TOP_RIGHT), Size(h, h)) self.uptime = App.uptime() self.uptime_widget = TextWidget(self, self._uptime(), Font(size=10)) self.uptime_widget.rectangle = Rectangle( self.position(Anchor.BOTTOM_LEFT), Size(s.left, self.uptime_widget.preferred_size.height)) self.app.scheduler.schedule_synchronous(timedelta(minutes=1), self._update_uptime) self._update_uptime()
def draw_rounded_rectangle(ctx: Context, rectangle: Rectangle, radius: float): """ Draw rectangles with rounded (circular arc) corners. :param ctx: Cairo render context. :param rectangle: Extent of the rectangle. :param radius: Radius of the corners. """ from math import pi a, c = rectangle.position(Anchor.TOP_LEFT) b, d = rectangle.position(Anchor.BOTTOM_RIGHT) ctx.arc(a + radius, c + radius, radius, 2 * (pi / 2), 3 * (pi / 2)) ctx.arc(b - radius, c + radius, radius, 3 * (pi / 2), 4 * (pi / 2)) ctx.arc(b - radius, d - radius, radius, 0 * (pi / 2), 1 * (pi / 2)) # ;o) ctx.arc(a + radius, d - radius, radius, 1 * (pi / 2), 2 * (pi / 2)) ctx.close_path()
def do_layout(self): for child in self.children: if isinstance(child, TextWidget): child.set_size(Size(self.width, child.height), Anchor.TOP_LEFT) self._icon_widget.rectangle = Rectangle( self.size.position(Anchor.CENTER_RIGHT) + Point(5, 0), self.size / 1.5)
def paint_foreground(self, ctx: Context): ctx.set_source_rgba(*Color.GRAY40) rect = Rectangle(ZERO_TOP_LEFT, self.size) center = rect.position(Anchor.CENTER_CENTER) r = min(*self.size) / 2 ctx.move_to(center.x, center.y) φ = 2 * math.pi * datetime.now().microsecond / 1000000 ctx.arc(*center, r, -math.pi / 2, φ - math.pi / 2) ctx.line_to(center.x, center.y) ctx.fill() ctx.move_to(center.x, center.y) ctx.line_to(center.x + r * math.sin(φ), center.y - r * math.cos(φ)) ctx.set_line_width(2) ctx.set_source_rgba(*Color.WHITE) ctx.stroke()
def __init__(self, parent: ContainerWidget, weather_period: Optional[WeatherPeriod], download_manager: DownloadManager, font: Font = Font(size=9, bold=True)): super().__init__(parent) self._download_manager = download_manager self.font = font self._icon_widget = ImageWidget(self) self._icon_widget.rectangle = Rectangle( self.size.position(Anchor.CENTER_RIGHT), self.size / 1.5) self._from_to_widget: TextWidget = TextWidget(self, '00:00-00:00', self.font) self._from_to_widget.rectangle = Rectangle( AnchoredPoint(0, 1, Anchor.TOP_LEFT), self._from_to_widget.preferred_size) self._from_to_widget.h_alignment = TextWidget.HAlignment.CENTER self._from_to_widget.background = None self._temp_widget = TextWidget( self, '-11.3°C', dataclasses.replace(self.font, size=self.font.size * 1.5)) self._temp_widget.rectangle = Rectangle( self._from_to_widget.position(Anchor.BOTTOM_LEFT).anchored( Anchor.TOP_LEFT) + Point(0, 4), self._temp_widget.preferred_size) self._temp_widget.foreground = Color.RED self._temp_widget.background = None self._cloudiness_widget = TextWidget(self, '0/0', self.font) self._cloudiness_widget.rectangle = Rectangle( self._temp_widget.position(Anchor.BOTTOM_LEFT).anchored( Anchor.TOP_LEFT) + Point(0, 4), self._cloudiness_widget.preferred_size) self._cloudiness_widget.background = None self._rain_widget = TextWidget(self, '22.3mm 100%', self.font) self._rain_widget.rectangle = Rectangle( self._cloudiness_widget.position(Anchor.BOTTOM_LEFT).anchored( Anchor.TOP_LEFT) + Point(0, 4), self._rain_widget.preferred_size) self._rain_widget.background = None self.weather_period = weather_period
def __init__(self, parent: ContainerWidget, weather_periods: Optional[List[WeatherPeriod]], download_manager: DownloadManager, font: Font = Font(size=12)): super().__init__(parent) self._weather_periods = weather_periods self._font = font for i in range(5): w = WeatherWidget(self, None, download_manager) w.rectangle = Rectangle( AnchoredPoint(i * (w.preferred_size.width + 3), 0, Anchor.TOP_LEFT), w.preferred_size) l: Line = Line(self, Line.Orientation.VERTICAL) l.foreground = Color.GRAY67 l.rectangle = Rectangle( w.position(Anchor.TOP_RIGHT).anchored(Anchor.TOP_LEFT), Size(l.preferred_size().width, w.height)) self._update_children()
def build_menu(self): self.children.clear() y = 0 for i in range(len(self.menu)): entry = self.menu[i] a = TextWidget(self, entry.text, self.font) a.rectangle = Rectangle(AnchoredPoint(0, y, Anchor.TOP_LEFT), a.preferred_size) if i == self.current_entry: a.foreground = self.background a.background = self.foreground entry.widget = a
def __init__(self, parent: AppWidget): super().__init__(parent, "Main") self.lv2_3 = Line(self, Line.Orientation.VERTICAL) self.lv2_3.rectangle = Rectangle(AnchoredPoint(self.width / 3 * 2, 0, Anchor.TOP_LEFT), Size(self.lv2_3.preferred_size().width, self.height)) self.date = TimeWidget(self, Config.DateTime.date_format(), h_alignment=TextWidget.HAlignment.CENTER) self.date.rectangle = Rectangle(self.lv2_3.position(Anchor.TOP_RIGHT).anchored(Anchor.TOP_LEFT), self.position(Anchor.BOTTOM_RIGHT)) self.date.font = dataclasses.replace(self.date.font, bold=True) self.date.fit_font_size() self.date.set_height(self.date.preferred_size.height, VAnchor.TOP) self.date.foreground = Color.GRAY75 self.time = TimeWidget(self, Config.DateTime.time_format()) self.time.rectangle = Rectangle(self.date.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT) + Point(0, 2), self.position(Anchor.BOTTOM_RIGHT)) self.time.fit_font_size() self.time.set_height(self.time.preferred_size.height, VAnchor.TOP) self.time.foreground = self.date.foreground self.lh1 = Line(self, Line.Orientation.HORIZONTAL) self.lh1.rectangle = Rectangle(self.time.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT), Size(self.width - self.lv2_3.left, self.lh1.preferred_size().height)) self.wetter_com = WetterCom(Config.Weather.city_code(), Global.download_manager) self.weather_widgets = WeatherWidgets(self, None, Global.download_manager) self.weather_widgets.rectangle = Rectangle(self.position(Anchor.BOTTOM_LEFT), self.weather_widgets.preferred_size) self.load_weather() self.app.scheduler.schedule_synchronous(timedelta(minutes=10), self.load_weather) temp_font = Font(size=11, bold=True) self.out_temp = TextWidget(self, 'Out: -00.0° - -00.0°', temp_font) self.out_temp.rectangle = Rectangle(self.weather_widgets.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT) + Point(0, -1), self.out_temp.preferred_size + Size(0, 2)) self.out_temp.foreground = Color.GRAY80 self.out_temp.escape = False self.in_temp = TextWidget(self, 'In: -00.0° - -00.0°', temp_font) self.in_temp.rectangle = Rectangle(AnchoredPoint(self.width, self.out_temp.top, Anchor.TOP_RIGHT), self.in_temp.preferred_size) self.in_temp.foreground = Color.GRAY80 self.in_temp.escape = False self.in_temp.h_alignment = TextWidget.HAlignment.RIGHT self.balcony_temp = TextWidget(self, 'Blk: -00.0°', temp_font) self.balcony_temp.rectangle = Rectangle(AnchoredPoint((self.out_temp.right + self.in_temp.left) / 2, self.out_temp.top, Anchor.TOP_CENTER), self.balcony_temp.preferred_size) self.balcony_temp.foreground = Color.GRAY80 self.balcony_temp.escape = False self.balcony_temp.h_alignment = TextWidget.HAlignment.CENTER def get_temp_values(_): Global.download_manager.get(Config.Weather.temp_values_url(), self.load_temp_values, timedelta(seconds=29)) get_temp_values(None) self.app.scheduler.schedule_synchronous(timedelta(minutes=1), get_temp_values) self.lh3 = Line(self, Line.Orientation.HORIZONTAL) self.lh3.rectangle = Rectangle(self.out_temp.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT) + Point(0, -1), Size(self.width, self.lh3.preferred_size().height)) self.track_title = MediaPlayerTrackTitleWidget(self, Global.media_player, Font(size=14)) self.track_title.rectangle = Rectangle(self.lh3.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT), Size(self.width, self.track_title.font.font_extents().height)) track_position_font = Font(size=11, bold=True) self.track_position = MediaPlayerTrackPositionWidget(self, Global.media_player, track_position_font) self.track_position.rectangle = Rectangle( self.track_title.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT), Size(self.width / 3, self.track_position.font.font_extents().height)) self.track_duration = MediaPlayerTrackDurationWidget(self, Global.media_player, track_position_font) self.track_duration.rectangle = Rectangle( self.track_title.position(Anchor.TOP_CENTER).anchored(Anchor.BOTTOM_CENTER), Size(self.width / 3, self.track_duration.font.font_extents().height)) self.track_duration.h_alignment = TextWidget.HAlignment.CENTER self.track_remaining = MediaPlayerTrackRemainingWidget(self, Global.media_player, track_position_font) self.track_remaining.rectangle = Rectangle( self.track_title.position(Anchor.TOP_RIGHT).anchored(Anchor.BOTTOM_RIGHT), Size(self.width / 3, self.track_remaining.font.font_extents().height)) self.track_remaining.h_alignment = TextWidget.HAlignment.RIGHT self.lh2 = Line(self, Line.Orientation.HORIZONTAL) self.lh2.rectangle = Rectangle( self.track_position.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT) + Point(0, -1), Size(self.width, self.lh2.preferred_size().height)) self.lv2_3.set_height(self.lh2.bottom, VAnchor.TOP) self.cpu_load_text = CpuLoadTextWidget(self, Font(size=12), h_alignment=TextWidget.HAlignment.CENTER) self.cpu_load_text.rectangle = Rectangle(self.lh2.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT) + Point(0, -1), self.cpu_load_text.preferred_size) self.cpu_load_text.foreground = Color.GRAY90 self.cpu_load_bar = CpuLoadBarWidget(self, BarWidget.Orientation.VERTICAL_UP, Color.GRAY75) self.cpu_load_bar.rectangle = Rectangle( self.cpu_load_text.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT), Size(self.cpu_load_text.width, self.cpu_load_text.top)) self.mem_stats_bar = MemStatsBar(self, Font(size=12), Color.GRAY75) self.mem_stats_bar.rectangle = \ Rectangle(AnchoredPoint(self.cpu_load_bar.right, 0, Anchor.TOP_LEFT) + Point(1, 0), Size(self.lv2_3.left - self.cpu_load_bar.right - 2, self.cpu_load_bar.width)) self.mem_stats_bar.foreground = Color.WHITE.with_value(alpha=0.9) self.disk_stats = DiskStats(self, Font(size=12)) self.disk_stats.rectangle = Rectangle(self.lv2_3.position(Anchor.BOTTOM_LEFT).anchored(Anchor.BOTTOM_RIGHT) + Point(0, -3), self.cpu_load_text.position(Anchor.TOP_RIGHT)) self.disk_stats.h_alignment = TextWidget.HAlignment.CENTER self.lhs = Line(self, Line.Orientation.HORIZONTAL) self.lhs.rectangle = Rectangle(self.disk_stats.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT) + Point(0, -1), Size(self.disk_stats.width, self.lhs.preferred_size().height)) self.lhs.foreground = Color.GRAY50 self.processes = ProcessList(self, 5, Font(size=12)) self.processes.rectangle = Rectangle(self.mem_stats_bar.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT) + Point(2, 5), self.lhs.position(Anchor.TOP_RIGHT)) self.fritz_box = FritzBox(self.app.scheduler, Config.FritzBox.address(), Config.FritzBox.password()) self.fritz_box_connected = FritzBoxConnectedWidget(self, self.fritz_box, Font(size=12)) self.fritz_box_connected.rectangle = Rectangle( self.lh1.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT) + Point(1, 1), Size(self.lh1.width, self.fritz_box_connected.preferred_size.height)) self.fritz_box_speed = FritzBoxSpeedWidget(self, self.fritz_box, Font(size=12)) self.fritz_box_speed.rectangle = Rectangle( self.fritz_box_connected.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT) + Point(0, 1), Size(self.fritz_box_connected.width, self.fritz_box_speed.preferred_size.height)) self.fritz_box_hosts = FritzBoxHostsWidget(self, self.fritz_box, Font(size=12)) self.fritz_box_hosts.rectangle = Rectangle( self.fritz_box_speed.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT) + Point(0, 1), Size(self.fritz_box_connected.width, self.fritz_box_hosts.preferred_size.height)) self.fritz_box_ip6 = FritzBoxIp6Widget(self, self.fritz_box, Font(size=12)) self.fritz_box_ip6.rectangle = Rectangle( self.lv2_3.position(Anchor.BOTTOM_RIGHT).anchored(Anchor.BOTTOM_LEFT) + Point(1, -3), Size(self.fritz_box_connected.width, self.fritz_box_ip6.preferred_size.height)) self.fritz_box_ip6.fit_font_size() self.fritz_box_ip6.set_height(self.fritz_box_ip6.preferred_size.height, VAnchor.BOTTOM) self.fritz_box_ip4 = FritzBoxIp4Widget(self, self.fritz_box, Font(size=12)) self.fritz_box_ip4.rectangle = Rectangle( self.fritz_box_ip6.position(Anchor.TOP_LEFT).anchored(Anchor.BOTTOM_LEFT) + Point(0, -1), Size(self.fritz_box_connected.width, self.fritz_box_ip4.preferred_size.height)) self.fritz_box_traffic = FritzBoxTrafficWidget(self, self.fritz_box, Font(size=12)) self.fritz_box_traffic.rectangle = Rectangle( self.fritz_box_hosts.position(Anchor.BOTTOM_LEFT).anchored(Anchor.TOP_LEFT) + Point(0, 1), self.fritz_box_ip4.position(Anchor.TOP_RIGHT) + Point(0, -1))
def set_size(self, size: Size, anchor: Anchor): self.rectangle = Rectangle(self.rectangle.position(anchor), size)
def set_position(self, point: AnchoredPoint): """ Moves this widget while retaining it's size. """ self.rectangle = Rectangle(point, self.size)
def rectangle(self) -> Rectangle: return Rectangle(ZERO_TOP_LEFT, self.screen_size)
def set_height(self, height: float, anchor: VAnchor): self.rectangle = Rectangle(self.position(anchor + HAnchor.LEFT), Size(self.width, height)) self.dirty = True
def set_width(self, width: float, anchor: HAnchor): self.rectangle = Rectangle(self.position(VAnchor.TOP + anchor), Size(width, self.width))
class Widget(ABC): """ Base class for all widgets. """ __metaclass__ = ABCMeta _parent: ContainerWidget _rectangle: Rectangle = clear19.widgets.geometry.ZERO_RECT _dirty: bool = True _background: Optional[Color] _foreground: Color def __init__(self, parent: ContainerWidget): """ :param parent: The container which holds this widget. """ self._background = parent.background self._foreground = parent.foreground self._parent = parent if parent is not self: parent.children.append(self) @property def parent(self) -> ContainerWidget: return self._parent @property def dirty(self) -> bool: """ :return: If true, this widget will be rendered soon. """ return self._dirty @dirty.setter def dirty(self, dirty: bool): """ If set to True, the parent is also set to dirty. """ if dirty != self._dirty: self._dirty = dirty if dirty and self.parent is not None: self.parent.dirty = True @property def rectangle(self) -> Rectangle: """ :return: The position of this widget in it's parent coordinates. """ return self._rectangle @rectangle.setter def rectangle(self, rectangle: Rectangle): self._rectangle = rectangle def position(self, anchor: Anchor) -> AnchoredPoint: """ :return: The position of the specified point in the parent's coordinates. """ return self._rectangle.position(anchor) def set_position(self, point: AnchoredPoint): """ Moves this widget while retaining it's size. """ self.rectangle = Rectangle(point, self.size) @property def size(self) -> Size: return self.rectangle.size def set_size(self, size: Size, anchor: Anchor): self.rectangle = Rectangle(self.rectangle.position(anchor), size) @property def background(self) -> Optional[Color]: return self._background @background.setter def background(self, background: Optional[Color]): self._background = background self.dirty = True @property def foreground(self) -> Color: return self._foreground @foreground.setter def foreground(self, foreground: Color): self._foreground = foreground self.dirty = True def paint(self, ctx: Context): """ Renders this widget. :param ctx: Cairo context. """ if self.background: ctx.set_source_rgba(*self.background) self.paint_background(ctx) ctx.set_source_rgba(*self.foreground) self.paint_foreground(ctx) self.dirty = False @abstractmethod def paint_foreground(self, ctx: Context): """ Renders this widget. Is called by Widget.paint. """ return def paint_background(self, ctx: Context): """ Renders the background of this widget. Is called by Widget.paint. """ ctx.rectangle(0, 0, *self.size) ctx.fill() @property def app(self) -> AppWidget: """ :return: The App object which is the root of the widget hierarchy. """ return self.parent.app def repaint(self): """ Sets the dirty flag. """ self.dirty = True @property def preferred_size(self) -> Size: """ :return: If overridden, the Size this widget should have to fit all it's content. """ return self.size def on_key_down(self, key: G19Key) -> bool: """ Receives key down events. :param key: The pressed key. :return: If True, the event will not be received by further widgets. """ return False def on_key_up(self, key: G19Key) -> bool: """ Receives key up events. :param key: The released key. :return: If True, the event will not be received by further widgets. """ return False @property def left(self) -> float: """ :return: Left edge of this widget in the parent's coordinates. """ return self._rectangle.left @property def right(self) -> float: """ :return: Right edge of this widget in the parent's coordinates. """ return self._rectangle.right @property def top(self) -> float: """ :return: Top edge of this widget in the parent's coordinates. """ return self._rectangle.top @property def bottom(self) -> float: """ :return: Bottom edge of this widget in the parent's coordinates. """ return self._rectangle.bottom @property def width(self) -> float: return self._rectangle.width def set_width(self, width: float, anchor: HAnchor): self.rectangle = Rectangle(self.position(VAnchor.TOP + anchor), Size(width, self.width)) @property def height(self) -> float: return self._rectangle.height def set_height(self, height: float, anchor: VAnchor): self.rectangle = Rectangle(self.position(anchor + HAnchor.LEFT), Size(self.width, height)) self.dirty = True def __str__(self) -> str: return f"{self.__class__.__name__}(rectangle={self.rectangle}, background={self.background}, " \ f"foreground={self.foreground})"