roundabout,
created on Thursday, 24 July 2025, 09:16:22 (1753348582),
received on Saturday, 9 August 2025, 12:22:37 (1754742157)
Author identity: vlad <vlad.muntoiu@gmail.com>
1e04534561d4a256b81ec2893bbc6a7311808e3a
applets/clock/__init__.py
@@ -40,6 +40,7 @@ class ClockApplet(panorama_panel.Applet):
# Create the monthly calendar
self.popover = Gtk.Popover()
self.track_popover(self.popover)
self.calendar = Gtk.Calendar()
self.calendar.set_show_week_numbers(True)
self.popover.set_child(self.calendar)
@@ -55,6 +56,7 @@ class ClockApplet(panorama_panel.Applet):
self.set_time()
self.context_menu = self.make_context_menu()
self.track_popover(self.context_menu)
right_click_controller = Gtk.GestureClick()
right_click_controller.set_button(3)
config.yaml
@@ -2,6 +2,8 @@ panels:
- position: top
monitor: 0
size: 40
autohide: false
hide_time: 300
applets:
left:
- LabelApplet:
main.py
@@ -3,6 +3,8 @@ from __future__ import annotations
import os
import sys
import importlib
import time
import traceback
import typing
from itertools import accumulate, chain
from pathlib import Path
@@ -61,6 +63,7 @@ class PanelConfigurator(Gtk.Frame):
bottom_position_radio: Gtk.CheckButton = Gtk.Template.Child()
left_position_radio: Gtk.CheckButton = Gtk.Template.Child()
right_position_radio: Gtk.CheckButton = Gtk.Template.Child()
autohide_switch: Gtk.Switch = Gtk.Template.Child()
def __init__(self, panel: Panel, **kwargs):
super().__init__(**kwargs)
@@ -81,6 +84,7 @@ class PanelConfigurator(Gtk.Frame):
self.bottom_position_radio.panel_position_target = Gtk.PositionType.BOTTOM
self.left_position_radio.panel_position_target = Gtk.PositionType.LEFT
self.right_position_radio.panel_position_target = Gtk.PositionType.RIGHT
self.autohide_switch.set_active(self.panel.autohide)
@Gtk.Template.Callback()
def update_panel_size(self, adjustment: Gtk.Adjustment):
@@ -88,6 +92,12 @@ class PanelConfigurator(Gtk.Frame):
return
self.panel.set_size(int(adjustment.get_value()))
@Gtk.Template.Callback()
def toggle_autohide(self, switch: Gtk.Switch, value: bool):
if not self.get_root():
return
self.panel.set_autohide(value, self.panel.hide_time)
@Gtk.Template.Callback()
def move_panel(self, button: Gtk.CheckButton):
if not self.get_root():
@@ -274,25 +284,37 @@ class AppletArea(Gtk.Box):
self.remove_css_class("panel-flash")
POSITION_TO_LAYER_SHELL_EDGE = {
Gtk.PositionType.TOP: Gtk4LayerShell.Edge.TOP,
Gtk.PositionType.BOTTOM: Gtk4LayerShell.Edge.BOTTOM,
Gtk.PositionType.LEFT: Gtk4LayerShell.Edge.LEFT,
Gtk.PositionType.RIGHT: Gtk4LayerShell.Edge.RIGHT,
}
class Panel(Gtk.Window):
def __init__(self, application: Gtk.Application, monitor: Gdk.Monitor, position: Gtk.PositionType = Gtk.PositionType.TOP, size: int = 40, monitor_index: int = 0):
def __init__(self, application: Gtk.Application, monitor: Gdk.Monitor, position: Gtk.PositionType = Gtk.PositionType.TOP, size: int = 40, monitor_index: int = 0, autohide: bool = False, hide_time: int = 0):
super().__init__(application=application)
self.drop_motion_controller = None
self.motion_controller = None
self.set_decorated(False)
self.position = None
self.autohide = None
self.monitor_index = monitor_index
self.hide_time = None
self.open_popovers: set[int] = set()
Gtk4LayerShell.init_for_window(self)
Gtk4LayerShell.set_monitor(self, monitor)
Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.TOP)
Gtk4LayerShell.auto_exclusive_zone_enable(self)
box = Gtk.CenterBox()
self.set_child(box)
self.set_position(position)
self.set_size(size)
self.set_autohide(autohide, hide_time)
self.left_area = AppletArea(orientation=box.get_orientation())
self.centre_area = AppletArea(orientation=box.get_orientation())
@@ -312,6 +334,7 @@ class Panel(Gtk.Window):
self.context_menu.set_parent(self)
self.context_menu.set_halign(Gtk.Align.START)
self.context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
panorama_panel.track_popover(self.context_menu)
right_click_controller = Gtk.GestureClick()
right_click_controller.set_button(3)
@@ -325,6 +348,10 @@ class Panel(Gtk.Window):
action_group.add_action(manager_action)
self.insert_action_group("panel", action_group)
def reset_margins(self):
for edge in POSITION_TO_LAYER_SHELL_EDGE.values():
Gtk4LayerShell.set_margin(self, edge, 0)
def set_edit_mode(self, value):
for area in (self.left_area, self.centre_area, self.right_area):
area.set_edit_mode(value)
@@ -339,6 +366,28 @@ class Panel(Gtk.Window):
self.context_menu.set_pointing_to(rect)
self.context_menu.popup()
def slide_in(self):
if not self.autohide:
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
return False
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) >= 0:
return False
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) + 1)
return True
def slide_out(self):
if not self.autohide:
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 0)
return False
if Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) <= 1 - self.size:
return False
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], Gtk4LayerShell.get_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position]) - 1)
return True
def show_manager(self, _0=None, _1=None):
print("Showing manager")
if self.get_application():
@@ -362,6 +411,7 @@ class Panel(Gtk.Window):
def set_position(self, position: Gtk.PositionType):
self.position = position
self.reset_margins()
match self.position:
case Gtk.PositionType.TOP:
Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, False)
@@ -390,6 +440,37 @@ class Panel(Gtk.Window):
case Gtk.PositionType.LEFT | Gtk.PositionType.RIGHT:
box.set_orientation(Gtk.Orientation.VERTICAL)
if self.autohide:
if not self.open_popovers:
GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out)
def set_autohide(self, autohide: bool, hide_time: int):
self.autohide = autohide
self.hide_time = hide_time
if not self.autohide:
self.reset_margins()
Gtk4LayerShell.auto_exclusive_zone_enable(self)
if self.motion_controller is not None:
self.remove_controller(self.motion_controller)
if self.drop_motion_controller is not None:
self.remove_controller(self.drop_motion_controller)
self.motion_controller = None
self.drop_motion_controller = None
self.reset_margins()
else:
Gtk4LayerShell.set_exclusive_zone(self, 0)
# Only leave 1px of the window as a "mouse sensor"
Gtk4LayerShell.set_margin(self, POSITION_TO_LAYER_SHELL_EDGE[self.position], 1 - self.size)
self.motion_controller = Gtk.EventControllerMotion()
self.add_controller(self.motion_controller)
self.drop_motion_controller = Gtk.DropControllerMotion()
self.add_controller(self.drop_motion_controller)
self.motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
self.drop_motion_controller.connect("enter", lambda *args: GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_in))
self.motion_controller.connect("leave", lambda *args: not self.open_popovers and GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out))
self.drop_motion_controller.connect("leave", lambda *args: not self.open_popovers and GLib.timeout_add(self.hide_time // (self.size - 1), self.slide_out))
def get_all_subclasses(klass: type) -> list[type]:
subclasses = []
for subclass in klass.__subclasses__():
@@ -468,12 +549,14 @@ class PanoramaPanel(Gtk.Application):
continue
monitor = self.monitors[monitor_index]
size = panel_data["size"]
autohide = panel_data["autohide"]
hide_time = panel_data["hide_time"]
panel = Panel(self, monitor, position, size, monitor_index)
panel = Panel(self, monitor, position, size, monitor_index, autohide, hide_time)
self.panels.append(panel)
panel.show()
print(f"{size}px panel on {position} edge of monitor {monitor_index}")
print(f"{size}px panel on {position} edge of monitor {monitor_index}, autohide is {autohide} ({hide_time}ms)")
for area_name, area in (("left", panel.left_area), ("centre", panel.centre_area), ("right", panel.right_area)):
applet_list = panel_data["applets"].get(area_name)
@@ -501,6 +584,8 @@ class PanoramaPanel(Gtk.Application):
"position": PANEL_POSITIONS_REVERSE[panel.position],
"monitor": panel.monitor_index,
"size": panel.size,
"autohide": panel.autohide,
"hide_time": panel.hide_time,
"applets": {}
}
panel-configurator.ui
@@ -32,6 +32,7 @@
<object class="GtkScale">
<property name="adjustment">
<object class="GtkAdjustment" id="panel_size_adjustment">
<property name="lower">12.0</property>
<property name="page-increment">1.0</property>
<property name="page-size">1.0</property>
<property name="step-increment">1.0</property>
@@ -68,6 +69,47 @@
</child>
</object>
</child>
<child>
<object class="GtkListBoxRow">
<property name="activatable">False</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Automatically _hide</property>
<property name="use-underline">True</property>
<property name="width-request">128</property>
<property name="wrap">True</property>
<property name="xalign">-0.0</property>
</object>
</child>
<child>
<object class="GtkSwitch" id="autohide_switch">
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="valign">center</property>
<signal name="state-set" handler="toggle_autohide"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Only show the panel while the cursor is over it or a menu is being used</property>
<property name="wrap">True</property>
<property name="xalign">0.0</property>
<attributes>
<attribute name="weight" value="light"/>
</attributes>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBoxRow">
<property name="activatable">False</property>
panel-manager.cmb
@@ -3,5 +3,5 @@
<!-- Created with Cambalache 0.96.1 -->
<cambalache-project version="0.96.0" target_tk="gtk-4.0">
<ui template-class="PanelManager" filename="panel-manager.ui" sha256="c16939e25edf779ee964dba64b4fc411ec8e7b45cb4c5707e403fca4c4be07e4"/>
<ui template-class="PanelConfigurator" filename="panel-configurator.ui" sha256="3b362de1d7ee0e20a58d8412b6bea07af8f7199095aa47ef1d462ba808ae391b"/>
<ui template-class="PanelConfigurator" filename="panel-configurator.ui" sha256="fb3cd5709bd849ea837ee74978a7d74b6b0cbd6dbc46e4237cb8bd5173bcc295"/>
</cambalache-project>
shared/panorama_panel.py
@@ -1,7 +1,7 @@
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, Gdk, GObject
from gi.repository import Gtk, Gdk, GObject, GLib
class Applet(Gtk.Box):
@@ -52,6 +52,28 @@ class Applet(Gtk.Box):
def restore_drag(self):
self.remove_controller(self.drag_source)
def track_popover(self, popover: Gtk.Popover):
popover.connect("show", lambda *args: _popover_shown(self, popover))
popover.connect("closed", lambda *args: _popover_hidden(self, popover))
def track_popover(popover: Gtk.Popover):
popover.connect("show", lambda *args: _popover_shown(None, popover))
popover.connect("closed", lambda *args: _popover_hidden(None, popover))
def _popover_shown(applet, popover: Gtk.Popover):
popover.get_root().open_popovers.add(id(popover))
if popover.get_root().autohide:
GLib.timeout_add(popover.get_root().hide_time // (popover.get_root().size - 1),
popover.get_root().slide_in)
def _popover_hidden(applet, popover: Gtk.Popover):
popover.get_root().open_popovers.remove(id(popover))
if popover.get_root().autohide and not popover.get_root().open_popovers:
GLib.timeout_add(popover.get_root().hide_time // (popover.get_root().size - 1),
popover.get_root().slide_out)
def get_panel_position(applet: Applet) -> Gtk.PositionType:
return applet.get_root().position