diff --git a/nix/module.nix b/nix/module.nix
index 3b25044..6c15238 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -291,6 +291,16 @@ in
bar.customModules.weather.scrollDown = mkStrOption "";
bar.customModules.weather.scrollUp = mkStrOption "";
bar.customModules.weather.unit = mkStrOption "imperial";
+ bar.customModules.worldclock.format = mkStrOption "%I:%M:%S %p %Z";
+ bar.customModules.worldclock.formatDiffDate = mkStrOption "%a %b %d %I:%M:%S %p %Z";
+ bar.customModules.worldclock.icon = mkStrOption "";
+ bar.customModules.worldclock.middleClick = mkStrOption "";
+ bar.customModules.worldclock.rightClick = mkStrOption "";
+ bar.customModules.worldclock.scrollDown = mkStrOption "";
+ bar.customModules.worldclock.scrollUp = mkStrOption "";
+ bar.customModules.worldclock.showIcon = mkBoolOption true;
+ bar.customModules.worldclock.showTime = mkBoolOption true;
+ bar.customModules.worldclock.tz = mkStrListOption ["America/New_York" "Europe/Paris" "Asia/Tokyo"];
bar.launcher.autoDetectIcon = mkBoolOption false;
bar.launcher.icon = mkStrOption "";
bar.launcher.middleClick = mkStrOption "";
@@ -507,6 +517,8 @@ in
theme.bar.buttons.modules.updates.spacing = mkStrOption "0.45em";
theme.bar.buttons.modules.weather.enableBorder = mkBoolOption false;
theme.bar.buttons.modules.weather.spacing = mkStrOption "0.45em";
+ theme.bar.buttons.modules.worldclock.enableBorder = mkBoolOption false;
+ theme.bar.buttons.modules.worldclock.spacing = mkStrOption "0.45em";
theme.bar.buttons.monochrome = mkBoolOption false;
theme.bar.buttons.network.enableBorder = mkBoolOption false;
theme.bar.buttons.network.spacing = mkStrOption "0.5em";
@@ -724,3 +736,4 @@ in
];
};
}
+
diff --git a/src/components/bar/exports.ts b/src/components/bar/exports.ts
index dbc3648..b4bdc32 100644
--- a/src/components/bar/exports.ts
+++ b/src/components/bar/exports.ts
@@ -25,6 +25,7 @@ import { Power } from '../../components/bar/modules/power/index';
import { Hyprsunset } from '../../components/bar/modules/hyprsunset/index';
import { Hypridle } from '../../components/bar/modules/hypridle/index';
import { Cava } from '../../components/bar/modules/cava/index';
+import { WorldClock } from '../../components/bar/modules/worldclock/index';
export {
Menu,
@@ -54,4 +55,5 @@ export {
Hyprsunset,
Hypridle,
Cava,
+ WorldClock,
};
diff --git a/src/components/bar/index.tsx b/src/components/bar/index.tsx
index 12f79b2..5025de9 100644
--- a/src/components/bar/index.tsx
+++ b/src/components/bar/index.tsx
@@ -26,6 +26,7 @@ import {
Hyprsunset,
Hypridle,
Cava,
+ WorldClock,
} from './exports';
import { WidgetContainer } from './shared/WidgetContainer';
@@ -67,6 +68,7 @@ const widget = {
hyprsunset: (): JSX.Element => WidgetContainer(Hyprsunset()),
hypridle: (): JSX.Element => WidgetContainer(Hypridle()),
cava: (): JSX.Element => WidgetContainer(Cava()),
+ worldclock: (): JSX.Element => WidgetContainer(WorldClock()),
};
const gdkMonitorMapper = new GdkMonitorMapper();
diff --git a/src/components/bar/modules/worldclock/index.tsx b/src/components/bar/modules/worldclock/index.tsx
new file mode 100644
index 0000000..892ff9d
--- /dev/null
+++ b/src/components/bar/modules/worldclock/index.tsx
@@ -0,0 +1,120 @@
+import { openMenu } from '../../utils/menu';
+import options from 'src/options';
+import { BarBoxChild } from 'src/lib/types/bar.js';
+import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js';
+import { bind, Variable } from 'astal';
+import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers';
+import { Astal } from 'astal/gtk3';
+import { systemTime } from 'src/globals/time';
+import { GLib } from 'astal';
+
+const { format, formatDiffDate, tz, icon, showIcon, showTime, rightClick, middleClick, scrollUp, scrollDown } =
+ options.bar.customModules.worldclock;
+const { style } = options.theme.bar.buttons;
+
+const time = Variable.derive(
+ [systemTime, format, formatDiffDate, tz],
+ (c, f, fdd, tzn) =>
+ tzn
+ .map((t) =>
+ c
+ .to_timezone(GLib.TimeZone.new(t))
+ .format(c.to_timezone(GLib.TimeZone.new(t)).get_day_of_year() == c.get_day_of_year() ? f : fdd),
+ )
+ .join(' | ') || '',
+);
+
+const WorldClock = (): BarBoxChild => {
+ const ClockTime = (): JSX.Element => ;
+ const ClockIcon = (): JSX.Element => (
+
+ );
+
+ const componentClassName = Variable.derive(
+ [bind(style), bind(showIcon), bind(showTime)],
+ (btnStyle, shwIcn, shwLbl) => {
+ const styleMap = {
+ default: 'style1',
+ split: 'style2',
+ wave: 'style3',
+ wave2: 'style3',
+ };
+ return `worldclock-container ${styleMap[btnStyle]} ${!shwLbl ? 'no-label' : ''} ${!shwIcn ? 'no-icon' : ''}`;
+ },
+ );
+
+ const componentChildren = Variable.derive([bind(showIcon), bind(showTime)], (shIcn, shTm) => {
+ if (shIcn && !shTm) {
+ return ;
+ } else if (shTm && !shIcn) {
+ return ;
+ }
+ return (
+
+
+
+
+ );
+ });
+
+ const component = (
+ {
+ componentClassName.drop();
+ componentChildren.drop();
+ }}
+ >
+ {componentChildren()}
+
+ );
+
+ return {
+ component,
+ isVisible: true,
+ boxClass: 'worldclock',
+ props: {
+ setup: (self: Astal.Button): void => {
+ let disconnectFunctions: (() => void)[] = [];
+
+ Variable.derive(
+ [
+ bind(rightClick),
+ bind(middleClick),
+ bind(scrollUp),
+ bind(scrollDown),
+ bind(options.bar.scrollSpeed),
+ ],
+ () => {
+ disconnectFunctions.forEach((disconnect) => disconnect());
+ disconnectFunctions = [];
+
+ const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get());
+
+ disconnectFunctions.push(
+ onPrimaryClick(self, (clicked, event) => {
+ openMenu(clicked, event, 'calendarmenu');
+ }),
+ );
+
+ disconnectFunctions.push(
+ onSecondaryClick(self, (clicked, event) => {
+ runAsyncCommand(rightClick.get(), { clicked, event });
+ }),
+ );
+
+ disconnectFunctions.push(
+ onMiddleClick(self, (clicked, event) => {
+ runAsyncCommand(middleClick.get(), { clicked, event });
+ }),
+ );
+
+ disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()));
+ },
+ );
+ },
+ },
+ };
+};
+
+export { WorldClock };
diff --git a/src/components/bar/settings/config.tsx b/src/components/bar/settings/config.tsx
index 70e2802..77d1c22 100644
--- a/src/components/bar/settings/config.tsx
+++ b/src/components/bar/settings/config.tsx
@@ -419,6 +419,29 @@ export const CustomModuleSettings = (): JSX.Element => {
+ {/* World Clock Section */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{/* Power Section */}
+ {/* World Clock Module Section */}
+
+
+
+
+
+
+
{/* Power Module Section */}
diff --git a/src/options.ts b/src/options.ts
index 145b7a0..d8ab3f3 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -435,6 +435,15 @@ const options = mkOptions({
icon_background: opt(colors.base2),
spacing: opt('0.5em'),
},
+ worldclock: {
+ enableBorder: opt(false),
+ border: opt(colors.yellow),
+ background: opt(colors.base2),
+ text: opt(colors.yellow),
+ icon: opt(colors.yellow),
+ icon_background: opt(colors.base2),
+ spacing: opt('0.5em'),
+ },
},
},
menus: {
@@ -1235,6 +1244,18 @@ const options = mkOptions({
scrollUp: opt(''),
scrollDown: opt(''),
},
+ worldclock: {
+ icon: opt(''),
+ showIcon: opt(true),
+ showTime: opt(true),
+ format: opt('%I:%M:%S %p %Z'),
+ formatDiffDate: opt('%a %b %d %I:%M:%S %p %Z'),
+ rightClick: opt(''),
+ middleClick: opt(''),
+ scrollUp: opt(''),
+ scrollDown: opt(''),
+ tz: opt(['America/New_York', 'Europe/Paris', 'Asia/Tokyo']),
+ },
},
},
diff --git a/src/scss/style/bar/clock.scss b/src/scss/style/bar/clock.scss
index 212f12f..0f531ee 100644
--- a/src/scss/style/bar/clock.scss
+++ b/src/scss/style/bar/clock.scss
@@ -58,3 +58,69 @@
0em
);
}
+
+.bar-button-label.worldclock {
+ color: if($bar-buttons-monochrome, $bar-buttons-text, $bar-buttons-modules-worldclock-text);
+ margin-left: $bar-buttons-modules-worldclock-spacing;
+}
+
+.bar-button-icon.worldclock {
+ font-size: 1.2em;
+ color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-modules-worldclock-icon);
+}
+
+.style2 {
+ .bar-button-icon.worldclock {
+ border-top-left-radius: $bar-buttons-radius;
+ border-bottom-left-radius: $bar-buttons-radius;
+ background: if(
+ $bar-buttons-monochrome,
+ $bar-buttons-icon_background,
+ $bar-buttons-modules-worldclock-icon_background
+ );
+ padding: $bar-buttons-padding_y 0em;
+ padding-left: $bar-buttons-padding_x;
+ padding-right: $bar-buttons-modules-worldclock-spacing;
+ border-top-left-radius: if(
+ $bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
+ $bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
+ $bar-buttons-radius
+ );
+ border-bottom-left-radius: if(
+ $bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
+ $bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
+ $bar-buttons-radius
+ );
+ color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-modules-worldclock-icon);
+ }
+
+ .bar-button-label.worldclock {
+ padding: $bar-buttons-padding_y 0em;
+ padding-right: $bar-buttons-padding_x;
+ padding-left: $bar-buttons-modules-worldclock-spacing;
+ margin-left: 0em;
+ }
+ &.no-label.worldclock-container {
+ .bar-button-icon.worldclock {
+ border-top-right-radius: if(
+ $bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
+ $bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
+ $bar-buttons-radius
+ );
+ border-bottom-right-radius: if(
+ $bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
+ $bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
+ $bar-buttons-radius
+ );
+ }
+ }
+}
+
+.bar_item_box_visible.worldclock {
+ border: if(
+ $bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
+ $bar-buttons-borderSize solid
+ if($bar-buttons-monochrome, $bar-buttons-borderColor, $bar-buttons-modules-worldclock-border),
+ 0em
+ );
+}
diff --git a/src/scss/style/bar/style.scss b/src/scss/style/bar/style.scss
index 873bfbd..b6967d5 100644
--- a/src/scss/style/bar/style.scss
+++ b/src/scss/style/bar/style.scss
@@ -503,3 +503,30 @@
// custom font size
1.2em //
);
+
+/*
+ * #################################
+ * # World Clock Styling #
+ * #################################
+ */
+@include styleModule(
+ //
+ // class name
+ 'worldclock',
+ // label color
+ $bar-buttons-modules-worldclock-text,
+ // icon color
+ $bar-buttons-modules-worldclock-icon,
+ // icon background if split style is used
+ $bar-buttons-modules-worldclock-icon_background,
+ // label background
+ $bar-buttons-modules-worldclock-background,
+ // inner spacing
+ $bar-buttons-modules-worldclock-spacing,
+ // if border enabled
+ $bar-buttons-modules-worldclock-enableBorder,
+ // border color
+ $bar-buttons-modules-worldclock-border,
+ // custom font size
+ 1.2em //
+);