1+ const Gio = imports . gi . Gio ;
2+ const St = imports . gi . St ;
3+ const Desklet = imports . ui . desklet ;
4+ const Mainloop = imports . mainloop ;
5+ const GLib = imports . gi . GLib ;
6+ const Gettext = imports . gettext ;
7+ const Settings = imports . ui . settings ;
8+ const Cinnamon = imports . gi . Cinnamon ;
9+ const Lang = imports . lang ;
10+ const Main = imports . ui . main ;
11+ const Clutter = imports . gi . Clutter ;
12+ const GdkPixbuf = imports . gi . GdkPixbuf ;
13+ const Cogl = imports . gi . Cogl ;
14+
15+ const UUID = "swatchtime@kdawson" ;
16+ const DESKLET_ROOT = imports . ui . deskletManager . deskletMeta [ UUID ] . path ;
17+
18+
19+ function MyDesklet ( metadata , desklet_id ) {
20+ this . _init ( metadata , desklet_id ) ;
21+ }
22+
23+ MyDesklet . prototype = {
24+ __proto__ : Desklet . Desklet . prototype ,
25+
26+ _init : function ( metadata , desklet_id ) {
27+ Desklet . Desklet . prototype . _init . call ( this , metadata , desklet_id ) ;
28+ this . metadata = metadata ;
29+
30+ this . settings = new Settings . DeskletSettings ( this , this . metadata . uuid , desklet_id ) ;
31+ this . settings . bind ( 'show-centibeats' , 'showCentibeats' , this . _onSettingsChanged ) ;
32+ this . settings . bind ( 'show-logo' , 'showLogo' , this . _onSettingsChanged ) ;
33+ this . settings . bind ( 'bg-color' , 'bgColor' , this . _onSettingsChanged ) ;
34+ this . settings . bind ( 'bg-opacity' , 'bgOpacity' , this . _onSettingsChanged ) ;
35+ this . settings . bind ( 'font-color' , 'fontColor' , this . _onSettingsChanged ) ;
36+ this . settings . bind ( 'font-size' , 'fontSize' , this . _onSettingsChanged ) ;
37+
38+ this . _onSettingsChanged ( ) ;
39+ this . setupUI ( ) ;
40+ this . _startTimer ( ) ;
41+ } ,
42+
43+ setupUI : function ( ) {
44+ // outer container
45+ this . container = new St . Bin ( { reactive : true } ) ;
46+
47+ // background box with padding and rounded style via inline CSS
48+ this . bg = new St . BoxLayout ( { style_class : 'swatch-bg' , vertical : false } ) ;
49+
50+ // flag icon
51+ this . flag = null ;
52+ if ( this . showLogo ) {
53+ try {
54+ let flagPath = DESKLET_ROOT + '/icon.png' ;
55+ let file = Gio . file_new_for_path ( flagPath ) ;
56+ if ( file . query_exists ( null ) ) {
57+ let gicon = new Gio . FileIcon ( { file : file } ) ;
58+ this . flag = new St . Icon ( { gicon : gicon , icon_size : Math . round ( this . fontSize * 0.9 ) } ) ;
59+ }
60+ } catch ( e ) { }
61+ }
62+
63+ // label for swatch beats
64+ this . label = new St . Label ( { text : '@000' , style_class : 'swatch-label' } ) ;
65+
66+ // center alignment container
67+ this . inner = new St . BoxLayout ( { style_class : 'swatch-content' , vertical : false , x_align : St . Align . MIDDLE } ) ;
68+ if ( this . flag ) this . inner . add_actor ( this . flag ) ;
69+ this . inner . add_actor ( this . label ) ;
70+
71+ this . bg . add_actor ( this . inner ) ;
72+
73+ // now that label and content exist, apply styles
74+ this . _applyStyles ( ) ;
75+
76+ this . wrapper = new St . BoxLayout ( { vertical : false } ) ;
77+ this . wrapper . add_actor ( this . bg ) ;
78+ this . container . add_actor ( this . wrapper ) ;
79+ this . setContent ( this . container ) ;
80+
81+ } ,
82+
83+ _applyStyles : function ( ) {
84+ if ( ! this . label ) {
85+ return ;
86+ }
87+
88+ // Make outer container and wrapper transparent so only the pill shows
89+ try {
90+ this . container . set_style ( 'background: transparent; padding: 0;' ) ;
91+ if ( this . wrapper ) this . wrapper . set_style ( 'background: transparent; padding: 0; align-items: center;' ) ;
92+ } catch ( e ) { }
93+
94+ // Inline style for the pill background
95+ const rgba = this . _colorToRgba ( this . bgColor , this . bgOpacity ) ;
96+ const pillStyle = 'background-color: ' + rgba + '; border-radius: 40px; padding: 8px 20px; box-shadow: 0 8px 20px rgba(0,0,0,0.6); display: flex; align-items: center;' ;
97+ this . bg . set_style ( pillStyle ) ;
98+ let margin = Math . round ( this . fontSize * 0.4 ) ;
99+ this . label . set_style (
100+ 'color: ' + this . fontColor + ';' +
101+ 'font-size: ' + Math . round ( this . fontSize ) + 'px;' +
102+ 'font-weight: 400;' +
103+ 'margin-left: ' + margin + 'px;' +
104+ 'margin-right: ' + margin + 'px;'
105+ ) ;
106+ } ,
107+
108+ _colorToRgba : function ( color , alpha ) {
109+ let r = 0 , g = 0 , b = 0 ;
110+ if ( color . startsWith ( '#' ) ) {
111+ // Parse hex (e.g., #rrggbb or #rrggbbaa, ignore alpha if present)
112+ color = color . replace ( '#' , '' ) ;
113+ if ( color . length >= 6 ) {
114+ r = parseInt ( color . substring ( 0 , 2 ) , 16 ) ;
115+ g = parseInt ( color . substring ( 2 , 4 ) , 16 ) ;
116+ b = parseInt ( color . substring ( 4 , 6 ) , 16 ) ;
117+ }
118+ } else {
119+ // Parse rgb(r,g,b) or rgba(r,g,b,a)
120+ const match = color . match ( / r g b a ? \( ( \d + ) , \s * ( \d + ) , \s * ( \d + ) (?: , \s * ( [ \d . ] + ) ) ? \) / ) ;
121+ if ( match ) {
122+ r = parseInt ( match [ 1 ] , 10 ) ;
123+ g = parseInt ( match [ 2 ] , 10 ) ;
124+ b = parseInt ( match [ 3 ] , 10 ) ;
125+ // We ignore the stored alpha and use the passed alpha
126+ }
127+ }
128+ // Fallback to black if parsing fails
129+ if ( isNaN ( r ) || isNaN ( g ) || isNaN ( b ) ) {
130+ return 'rgba(0,0,0,' + alpha + ')' ;
131+ }
132+ return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')' ;
133+ } ,
134+
135+ _startTimer : function ( ) {
136+ if ( this . _timeout ) Mainloop . source_remove ( this . _timeout ) ;
137+ this . _update ( ) ;
138+ // update every 1 second
139+ this . _timeout = Mainloop . timeout_add_seconds ( 1 , ( ) => {
140+ this . _update ( ) ;
141+ return true ; // repeat
142+ } ) ;
143+ } ,
144+
145+ _update : function ( ) {
146+ const beats = this . _calculateSwatchTime ( new Date ( ) ) ;
147+ let beatInteger = Math . floor ( beats ) ; // 0 to 999
148+ let text ;
149+ if ( this . showCentibeats ) {
150+ let paddedInteger = String ( beatInteger ) . padStart ( 3 , '0' ) ;
151+ let fractional = ( beats - beatInteger ) . toFixed ( 2 ) . substring ( 1 ) ;
152+ text = `@${ paddedInteger } ${ fractional } ` ;
153+ } else {
154+ // Always 3 digits with leading zeros (e.g. @000)
155+ text = '@' + String ( beatInteger ) . padStart ( 3 , '0' ) ;
156+ }
157+ if ( this . label && typeof this . label . set_text === 'function' ) {
158+ this . label . set_text ( text ) ;
159+ }
160+ return true ;
161+ } ,
162+
163+ _calculateSwatchTime : function ( date ) {
164+ // Calculate beats using Biel Mean Time (UTC+1)
165+ const utcHours = date . getUTCHours ( ) ;
166+ const utcMinutes = date . getUTCMinutes ( ) ;
167+ const utcSeconds = date . getUTCSeconds ( ) ;
168+ const utcMilliseconds = date . getUTCMilliseconds ( ) ;
169+ // Convert to BMT (UTC+1)
170+ const bmtHours = ( utcHours + 1 ) % 24 ;
171+ // total seconds since midnight BMT
172+ const totalSeconds = ( bmtHours * 3600 ) + ( utcMinutes * 60 ) + utcSeconds + ( utcMilliseconds / 1000 ) ;
173+ const beats = ( totalSeconds / 86.4 ) % 1000 ; // 86400 / 1000 = 86.4
174+ return beats ;
175+ } ,
176+
177+
178+
179+ _onSettingsChanged : function ( ) {
180+ // Handle logo show/hide
181+ if ( this . showLogo && ! this . flag ) {
182+ try {
183+ let flagPath = DESKLET_ROOT + '/icon.png' ;
184+ let file = Gio . file_new_for_path ( flagPath ) ;
185+ if ( file . query_exists ( null ) ) {
186+ let gicon = new Gio . FileIcon ( { file : file } ) ;
187+ this . flag = new St . Icon ( { gicon : gicon , icon_size : Math . round ( this . fontSize * 0.9 ) } ) ;
188+ this . inner . insert_actor ( this . flag , 0 ) ;
189+ }
190+ } catch ( e ) { }
191+ } else if ( ! this . showLogo && this . flag ) {
192+ this . inner . remove_actor ( this . flag ) ;
193+ this . flag = null ;
194+ }
195+ // Update icon size if font size changed
196+ if ( this . flag ) {
197+ this . flag . set_icon_size ( Math . round ( this . fontSize * 0.9 ) ) ;
198+ }
199+ this . _applyStyles ( ) ;
200+ this . _update ( ) ;
201+ } ,
202+
203+ on_desklet_removed : function ( ) {
204+ if ( this . _timeout ) Mainloop . source_remove ( this . _timeout ) ;
205+ }
206+ } ;
207+
208+ function main ( metadata , desklet_id ) {
209+ return new MyDesklet ( metadata , desklet_id ) ;
210+ }
0 commit comments