1+ import PySimpleGUI as sg
2+ import sys
3+ import datetime
4+
5+ """
6+ Desktop Widget - Template to start with
7+ This "template" is meant to give you a starting point towards making your own Desktop Widget
8+ Note - the term "Widget" here means a "Desktop Widget", not a GUI Widget
9+
10+ It has many of the features that a Rainmeter-style Desktop Widget would have
11+ * Save position of window
12+ * Set Alpha channel
13+ * "Edit Me" which will launch your editor to edit the code
14+ * Right click menu to access all setup
15+ * Theme selection
16+ * Preview of window using a different theme
17+ * A command line parm to set the intial position of the window in case one hasn't been saved
18+ * A status section of the window that can be hidden / restored (currently shows last refresh time)
19+ * A title
20+ * A main display area
21+
22+ The contents of your widget may be significantly different than this example. Change the function
23+ make_window to create your own custom layout and window.
24+
25+ There are several important design patterns provided including:
26+ Using a function to define and create your window
27+ Using User Settings APIs to save program settings
28+ A Theme Selection window with previewing capability
29+
30+ The standard PySimpleGUI Coding Conventions are used throughout including
31+ * Naming layout keys in format '-KEY-'
32+ * Naming User Settings keys in the format '-key-'
33+ * Using standard layout, window, event, values variable names
34+
35+ Copyright 2021 PySimpleGUI
36+ """
37+
38+ ALPHA = 0.9 # Initial alpha until user changes
39+ THEME = 'Dark green 3' # Initial theme until user changes
40+ refresh_font = title_font = 'Courier 8'
41+ main_info_font = 'Courier 20'
42+ main_info_size = (10 ,1 )
43+ UPDATE_FREQUENCY_MILLISECONDS = 1000 * 60 * 60 # update every hour by default until set by user
44+
45+ def choose_theme (location , size ):
46+ """
47+ A window to allow new themes to be tried out.
48+ Changes the theme to the newly chosen one and returns theme's name
49+ Automaticallyi switches to new theme and saves the setting in user settings file
50+
51+ :param location: (x,y) location of the Widget's window
52+ :type location: Tuple[int, int]
53+ :param size: Size in pixels of the Widget's window
54+ :type size: Tuple[int, int]
55+ :return: The name of the newly selected theme
56+ :rtype: None | str
57+ """
58+ layout = [[sg .Text ('Try a theme' )],
59+ [sg .Listbox (values = sg .theme_list (), size = (20 , 20 ), key = '-LIST-' , enable_events = True )],
60+ [sg .OK (), sg .Cancel ()]]
61+
62+ window = sg .Window ('Look and Feel Browser' , layout , location = location )
63+ old_theme = sg .theme ()
64+ while True : # Event Loop
65+ event , values = window .read ()
66+ if event in (sg .WIN_CLOSED , 'Exit' , 'OK' , 'Cancel' ):
67+ break
68+ sg .theme (values ['-LIST-' ][0 ])
69+ window .hide ()
70+ # make at test window to the left of the current one
71+ test_window = make_window (location = ((location [0 ]- size [0 ]* 1.2 , location [1 ])), test_window = True )
72+ test_window .read (close = True )
73+ if sg .popup_yes_no (f'Do you want to keep { values ["-LIST-" ]} ?' , location = location ) == 'Yes' :
74+ break
75+ window .un_hide ()
76+ window .close ()
77+
78+ # after choice made, save theme or restore the old one
79+ if event not in ('Cancel' , sg .WIN_CLOSED ) and values ['-LIST-' ]:
80+ sg .theme (values ['-LIST-' ][0 ])
81+ sg .user_settings_set_entry ('-theme-' , values ['-LIST-' ][0 ])
82+ return values ['-LIST-' ][0 ]
83+ else :
84+ sg .theme (old_theme )
85+ return None
86+
87+ def make_window (location , test_window = False ):
88+ """
89+ Defines the layout and creates the window for the main window
90+ If the parm test_window is True, then a simplified, and EASY to close version is shown
91+
92+ :param location: (x,y) location to create the window
93+ :type location: Tuple[int, int]
94+ :param test_window: If True, then this is a test window & will close by clicking on it
95+ :type test_window: bool
96+ :return: newly created window
97+ :rtype: sg.Window
98+ """
99+ title = sg .user_settings_get_entry ('-title-' , '' )
100+ if not test_window :
101+ theme = sg .user_settings_get_entry ('-theme-' , THEME )
102+ sg .theme (theme )
103+
104+ # ------------------- Window Layout -------------------
105+
106+ if test_window :
107+ title_element = sg .Text ('Click to close' , font = title_font , enable_events = True )
108+ right_click_menu = [['' ], ['Exit' ,]]
109+ else :
110+ title_element = sg .Text (title , size = (20 , 1 ), font = title_font , justification = 'c' , k = '-TITLE-' )
111+ right_click_menu = [['' ], ['Choose Title' , 'Edit Me' , 'New Theme' , 'Save Location' , 'Refresh' , 'Set Refresh Rate' , 'Show Refresh Info' , 'Hide Refresh Info' , 'Alpha' , [str (x ) for x in range (1 , 11 )], 'Exit' , ]]
112+
113+ layout = [[title_element ],
114+ [sg .Text ('0' , size = main_info_size , font = main_info_font , k = '-MAIN INFO-' , justification = 'c' , enable_events = test_window )],
115+ [sg .pin (sg .Text (size = (15 , 2 ), font = refresh_font , k = '-REFRESHED-' , justification = 'c' , visible = sg .user_settings_get_entry ('-show refresh-' , True )))]]
116+
117+ # ------------------- Window Creation -------------------
118+ return sg .Window ('Desktop Widget Template' , layout , location = location , no_titlebar = True , grab_anywhere = True , margins = (0 , 0 ), element_justification = 'c' ,
119+ element_padding = (0 , 0 ), alpha_channel = sg .user_settings_get_entry ('-alpha-' , ALPHA ), finalize = True , right_click_menu = right_click_menu )
120+
121+
122+ def main (location ):
123+ """
124+ Where execution begins
125+ The Event Loop lives here, but the window creation is done in another function
126+ This is an important design pattern
127+
128+ :param location: Location to create the main window if one is not found in the user settings
129+ :type location: Tuple[int, int]
130+ """
131+
132+ window = make_window (sg .user_settings_get_entry ('-location-' , location ))
133+
134+ refresh_frequency = sg .user_settings_get_entry ('-fresh frequency-' , UPDATE_FREQUENCY_MILLISECONDS )
135+
136+ while True : # Event Loop
137+ # Normally a window.read goes here, but first we're updating the values in the window, then reading it
138+ # First update the status information
139+ window ['-MAIN INFO-' ].update ('Your Info' )
140+ # for debugging show the last update date time
141+ window ['-REFRESHED-' ].update (datetime .datetime .now ().strftime ("%m/%d/%Y\n %I:%M:%S %p" ))
142+
143+ # -------------- Start of normal event loop --------------
144+ event , values = window .read (timeout = refresh_frequency )
145+ print (event , values )
146+ if event in (sg .WIN_CLOSED , 'Exit' ): # standard exit test... ALWAYS do this
147+ break
148+ if event == 'Edit Me' :
149+ sg .execute_editor (__file__ )
150+ elif event == 'Choose Title' :
151+ new_title = sg .popup_get_text ('Choose a title for your Widget' , location = window .current_location ())
152+ if new_title is not None :
153+ window ['-TITLE-' ].update (new_title )
154+ sg .user_settings_set_entry ('-title-' , new_title )
155+ elif event == 'Show Refresh Info' :
156+ window ['-REFRESHED-' ].update (visible = True )
157+ sg .user_settings_set_entry ('-show refresh-' , True )
158+ elif event == 'Save Location' :
159+ sg .user_settings_set_entry ('-location-' , window .current_location ())
160+ elif event == 'Hide Refresh Info' :
161+ window ['-REFRESHED-' ].update (visible = False )
162+ sg .user_settings_set_entry ('-show refresh-' , False )
163+ elif event in [str (x ) for x in range (1 , 11 )]: # if Alpha Channel was chosen
164+ window .set_alpha (int (event ) / 10 )
165+ sg .user_settings_set_entry ('-alpha-' , int (event ) / 10 )
166+ elif event == 'Set Refresh Rate' :
167+ choice = sg .popup_get_text ('How frequently to update window in seconds? (can be a float)' , default_text = sg .user_settings_get_entry ('-fresh frequency-' , UPDATE_FREQUENCY_MILLISECONDS )/ 1000 , location = window .current_location ())
168+ if choice is not None :
169+ try :
170+ refresh_frequency = float (choice )* 1000 # convert to milliseconds
171+ sg .user_settings_set_entry ('-fresh frequency-' , float (refresh_frequency ))
172+ except Exception as e :
173+ sg .popup_error (f'You entered an incorrect number of seconds: { choice } ' , f'Error: { e } ' , location = window .current_location ())
174+ elif event == 'New Theme' :
175+ loc = window .current_location ()
176+ if choose_theme (window .current_location (), window .size ) is not None :
177+ window .close () # out with the old...
178+ window = make_window (loc ) # in with the new
179+
180+ window .close ()
181+
182+
183+ if __name__ == '__main__' :
184+ # To start the window at a specific location, get this location on the command line
185+ # The location should be in form x,y with no spaces
186+ location = (None , None ) # assume no location provided
187+ if len (sys .argv ) > 1 :
188+ location = sys .argv [1 ].split (',' )
189+ location = (int (location [0 ]), int (location [1 ]))
190+ main (location )
0 commit comments