diff --git a/build.gradle b/build.gradle index 8b5a59f..eee0cb0 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,8 @@ dependencies { compile 'net.java.dev.jna:jna:4.0.0' compile 'javax.xml.bind:jaxb-api:2.3.1' compile 'com.squareup.retrofit2:converter-simplexml:2.3.0' + + implementation 'com.formdev:flatlaf:0.30' } jar { diff --git a/resources/sheepit-logo.png b/resources/sheepit-logo.png new file mode 100644 index 0000000..bbaeff6 Binary files /dev/null and b/resources/sheepit-logo.png differ diff --git a/resources/title.png b/resources/title.png deleted file mode 100644 index e52fffd..0000000 Binary files a/resources/title.png and /dev/null differ diff --git a/src/com/sheepit/client/Configuration.java b/src/com/sheepit/client/Configuration.java index 9987067..df983e2 100644 --- a/src/com/sheepit/client/Configuration.java +++ b/src/com/sheepit/client/Configuration.java @@ -64,6 +64,7 @@ public class Configuration { private boolean autoSignIn; private String UIType; private String hostname; + private String theme; public Configuration(File cache_dir_, String login_, String password_) { this.configFilePath = null; @@ -89,6 +90,7 @@ public class Configuration { this.extras = ""; this.autoSignIn = false; this.UIType = null; + this.theme = null; } diff --git a/src/com/sheepit/client/SettingsLoader.java b/src/com/sheepit/client/SettingsLoader.java index 1e6cc3c..25df28d 100644 --- a/src/com/sheepit/client/SettingsLoader.java +++ b/src/com/sheepit/client/SettingsLoader.java @@ -56,6 +56,7 @@ public class SettingsLoader { private String cacheDir; private String autoSignIn; private String ui; + private String theme; private int priority; public SettingsLoader(String path_) { @@ -67,7 +68,7 @@ public class SettingsLoader { } } - public SettingsLoader(String path_, String login_, String password_, String proxy_, String hostname_, ComputeType computeMethod_, GPUDevice gpu_, int cores_, long maxRam_, int maxRenderTime_, String cacheDir_, boolean autoSignIn_, String ui_, int priority_) { + public SettingsLoader(String path_, String login_, String password_, String proxy_, String hostname_, ComputeType computeMethod_, GPUDevice gpu_, int cores_, long maxRam_, int maxRenderTime_, String cacheDir_, boolean autoSignIn_, String ui_, String theme_, int priority_) { if (path_ == null) { path = getDefaultFilePath(); } @@ -82,6 +83,8 @@ public class SettingsLoader { autoSignIn = String.valueOf(autoSignIn_); ui = ui_; priority = priority_; + theme = theme_; + if (cores_ > 0) { cores = String.valueOf(cores_); } @@ -166,6 +169,10 @@ public class SettingsLoader { if (ui != null) { prop.setProperty("ui", ui); } + + if (theme != null) { + prop.setProperty("theme", theme); + } prop.store(output, null); } @@ -212,6 +219,7 @@ public class SettingsLoader { this.priority = 19; // must be the same default as Configuration this.ram = null; this.renderTime = null; + this.theme = null; if (new File(path).exists() == false) { return; @@ -275,6 +283,10 @@ public class SettingsLoader { this.ui = prop.getProperty("ui"); } + if (prop.containsKey("theme")) { + this.theme = prop.getProperty("theme"); + } + if (prop.containsKey("priority")) { this.priority = Integer.parseInt(prop.getProperty("priority")); } @@ -357,12 +369,20 @@ public class SettingsLoader { if (config.getUIType() == null && ui != null) { config.setUIType(ui); } - + + if (config.getTheme() == null) { + if (this.theme != null) { + config.setTheme(this.theme); + } else { + config.setTheme("light"); + } + } + config.setAutoSignIn(Boolean.valueOf(autoSignIn)); } @Override public String toString() { - return "SettingsLoader [path=" + path + ", login=" + login + ", password=" + password + ", computeMethod=" + computeMethod + ", gpu=" + gpu + ", cacheDir=" + cacheDir + "priority="+priority+"]"; + return "SettingsLoader [path=" + path + ", login=" + login + ", password=" + password + ", computeMethod=" + computeMethod + ", gpu=" + gpu + ", cacheDir=" + cacheDir + ", theme=" + theme + ", priority="+priority+"]"; } } diff --git a/src/com/sheepit/client/standalone/GuiSwing.java b/src/com/sheepit/client/standalone/GuiSwing.java index 355185f..f689795 100644 --- a/src/com/sheepit/client/standalone/GuiSwing.java +++ b/src/com/sheepit/client/standalone/GuiSwing.java @@ -25,7 +25,6 @@ import java.awt.Image; import java.awt.MenuItem; import java.awt.PopupMenu; import java.awt.SystemTray; -import java.awt.Toolkit; import java.awt.TrayIcon; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -57,6 +56,10 @@ import com.sheepit.client.standalone.swing.activity.Working; import lombok.Getter; import lombok.Setter; +import com.formdev.flatlaf.FlatLightLaf; // Required for dark & light mode +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatLaf; + public class GuiSwing extends JFrame implements Gui { public static final String type = "swing"; @@ -104,13 +107,6 @@ public class GuiSwing extends JFrame implements Gui { @Override public void start() { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e1) { - e1.printStackTrace(); - } - if (useSysTray) { try { sysTray = SystemTray.getSystemTray(); @@ -158,6 +154,20 @@ public class GuiSwing extends JFrame implements Gui { this.showActivity(ActivityType.SETTINGS); + try { + if (client.getConfiguration().getTheme().equals("light")) { + UIManager.setLookAndFeel(new FlatLightLaf()); + } else if (client.getConfiguration().getTheme().equals("dark")) { + UIManager.setLookAndFeel(new FlatDarkLaf()); + } + + // Apply the selected theme to swing components + FlatLaf.updateUI(); + } + catch (UnsupportedLookAndFeelException e1) { + e1.printStackTrace(); + } + while (waitingForAuthentication) { try { synchronized (this) { diff --git a/src/com/sheepit/client/standalone/Worker.java b/src/com/sheepit/client/standalone/Worker.java index 6f6420d..91ff585 100644 --- a/src/com/sheepit/client/standalone/Worker.java +++ b/src/com/sheepit/client/standalone/Worker.java @@ -112,6 +112,9 @@ public class Worker { @Option(name = "-title", usage = "Custom title for the GUI Client", required = false) private String title = "SheepIt Render Farm"; + @Option(name = "-theme", usage = "Specify the theme to use for the graphical client, default 'light', available 'light', 'dark'", required = false) + private String theme = null; + public static void main(String[] args) { new Worker().doMain(args); } @@ -283,6 +286,16 @@ public class Worker { config.setUIType(ui_type); } + if (theme != null) { + if (!theme.equals("light") && !theme.equals("dark")) { + System.err.println(String.format("The theme specified (%s) doesn't exist. Please choose 'light' or 'dark'.", theme)); + System.err.println("Aborting"); + System.exit(2); + } + + config.setTheme(this.theme); + } + if (config_file != null) { if (new File(config_file).exists() == false) { System.err.println("Configuration file not found."); diff --git a/src/com/sheepit/client/standalone/swing/activity/Settings.java b/src/com/sheepit/client/standalone/swing/activity/Settings.java index 55fc4de..42bdf00 100644 --- a/src/com/sheepit/client/standalone/swing/activity/Settings.java +++ b/src/com/sheepit/client/standalone/swing/activity/Settings.java @@ -35,6 +35,7 @@ import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; @@ -43,11 +44,18 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; +import javax.swing.JRadioButton; import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.JTextField; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; import javax.swing.SpinnerNumberModel; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.FlatDarkLaf; + import com.sheepit.client.Configuration; import com.sheepit.client.Configuration.ComputeType; import com.sheepit.client.SettingsLoader; @@ -78,6 +86,10 @@ public class Settings implements Activity { private JTextField proxy; private JTextField hostname; + private ButtonGroup themeOptionsGroup; + private JRadioButton lightMode; + private JRadioButton darkMode; + private JCheckBox saveFile; private JCheckBox autoSignIn; JButton saveButton; @@ -96,11 +108,13 @@ public class Settings implements Activity { Configuration config = parent.getConfiguration(); new SettingsLoader(config.getConfigFilePath()).merge(config); + applyTheme(config.getTheme()); // apply the proper theme (light/dark) + List gpus = GPU.listDevices(config); GridBagConstraints constraints = new GridBagConstraints(); int currentRow = 0; - ImageIcon image = new ImageIcon(getClass().getResource("/title.png")); + ImageIcon image = new ImageIcon(getClass().getResource("/sheepit-logo.png")); constraints.fill = GridBagConstraints.CENTER; JLabel labelImage = new JLabel(image); @@ -111,6 +125,11 @@ public class Settings implements Activity { ++currentRow; + constraints.gridy = currentRow; + parent.getContentPane().add(new JLabel(" "), constraints); // Add a separator between logo and first panel + + currentRow++; + // authentication CollapsibleJPanel authentication_panel = new CollapsibleJPanel(new GridLayout(2, 2)); authentication_panel.setBorder(BorderFactory.createTitledBorder("Authentication")); @@ -137,6 +156,36 @@ public class Settings implements Activity { constraints.fill = GridBagConstraints.HORIZONTAL; parent.getContentPane().add(authentication_panel, constraints); + // Theme selection panel + CollapsibleJPanel themePanel = new CollapsibleJPanel(new GridLayout(1, 3)); + themePanel.setBorder(BorderFactory.createTitledBorder("Theme")); + + themeOptionsGroup = new ButtonGroup(); + + lightMode = new JRadioButton("Light"); + lightMode.setActionCommand("light"); + lightMode.setSelected(config.getTheme().equals("light")); + lightMode.addActionListener(new ApplyThemeAction()); + + darkMode = new JRadioButton("Dark"); + darkMode.setActionCommand("dark"); + darkMode.setSelected(config.getTheme().equals("dark")); + darkMode.addActionListener(new ApplyThemeAction()); + + themePanel.add(lightMode); + themePanel.add(darkMode); + + // Group both radio buttons to allow only one selected + themeOptionsGroup.add(lightMode); + themeOptionsGroup.add(darkMode); + + currentRow++; + constraints.gridx = 0; + constraints.gridy = currentRow; + constraints.gridwidth = 2; + + parent.getContentPane().add(themePanel, constraints); + // directory CollapsibleJPanel directory_panel = new CollapsibleJPanel(new GridLayout(1, 3)); directory_panel.setBorder(BorderFactory.createTitledBorder("Cache")); @@ -370,6 +419,11 @@ public class Settings implements Activity { constraints.gridwidth = 2; parent.getContentPane().add(general_panel, constraints); + currentRow++; + constraints.gridy = currentRow; + parent.getContentPane().add(new JLabel(" "), constraints); // Add a separator between last checkboxes and button + + currentRow++; String buttonText = "Start"; if (parent.getClient() != null) { if (parent.getClient().isRunning()) { @@ -406,6 +460,22 @@ public class Settings implements Activity { saveButton.setEnabled(selected); return selected; } + + private void applyTheme(String theme_) { + try { + if (theme_.equals("light")) { + UIManager.setLookAndFeel(new FlatLightLaf()); + } else if (theme_.equals("dark")) { + UIManager.setLookAndFeel(new FlatDarkLaf()); + } + + // Apply the new theme + FlatLaf.updateUI(); + } + catch (UnsupportedLookAndFeelException e1) { + e1.printStackTrace(); + } + } class ChooseFileAction implements ActionListener { @@ -452,6 +522,13 @@ public class Settings implements Activity { } } + class ApplyThemeAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + applyTheme(themeOptionsGroup.getSelection().getActionCommand()); + } + } + class SaveAction implements ActionListener { @Override @@ -464,6 +541,9 @@ public class Settings implements Activity { if (config == null) { return; } + + if (themeOptionsGroup.getSelection().getActionCommand() != null) + config.setTheme(themeOptionsGroup.getSelection().getActionCommand()); if (cacheDir != null) { File fromConfig = config.getStorageDir(); @@ -550,7 +630,22 @@ public class Settings implements Activity { } if (saveFile.isSelected()) { - parent.setSettingsLoader(new SettingsLoader(config.getConfigFilePath(), login.getText(), new String(password.getPassword()), proxyText, hostnameText, method, selected_gpu, cpu_cores, max_ram, max_rendertime, cachePath, autoSignIn.isSelected(), GuiSwing.type, priority.getValue())); + parent.setSettingsLoader(new SettingsLoader( + config.getConfigFilePath(), + login.getText(), + new String(password.getPassword()), + proxyText, + hostnameText, + method, + selected_gpu, + cpu_cores, + max_ram, + max_rendertime, + cachePath, + autoSignIn.isSelected(), + GuiSwing.type, + themeOptionsGroup.getSelection().getActionCommand(), // selected theme + priority.getValue())); // wait for successful authentication (to store the public key) // or do we already have one? diff --git a/src/com/sheepit/client/standalone/swing/activity/Working.java b/src/com/sheepit/client/standalone/swing/activity/Working.java index 5599cd3..2c0b116 100644 --- a/src/com/sheepit/client/standalone/swing/activity/Working.java +++ b/src/com/sheepit/client/standalone/swing/activity/Working.java @@ -40,6 +40,7 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.UIManager; import javax.swing.Spring; import javax.swing.SpringLayout; @@ -73,6 +74,7 @@ public class Working implements Activity { private JLabel waiting_projects_value; private JLabel connected_machines_value; private JLabel user_info_total_rendertime_this_session_value; + private String currentTheme; public Working(GuiSwing parent_) { parent = parent_; @@ -92,10 +94,38 @@ public class Working implements Activity { user_info_total_rendertime_this_session_value = new JLabel(""); lastRenderTime = new JLabel(""); lastRender = new JLabel(""); + currentTheme = UIManager.getLookAndFeel().getName(); // Capture the theme on component instantiation } @Override public void show() { + // If the stored theme and the UIManager's theme doesn't match is bc the user has changed it + if (!currentTheme.equals(UIManager.getLookAndFeel().getName())) { + // And, as the user has changed the theme, then we must recreate all the UI elements with session data + // Reason being they are defined as class variables and therefore created once when the object + // is created the first time. + // As the Java swing engine applies the "look & feel" at creation time, we need to "re-create" the + // objects to ensure they have the right theme colors. + statusContent = new JLabel(statusContent.getText()); + renderedFrameContent = new JLabel(renderedFrameContent.getText()); + remainingFrameContent = new JLabel(remainingFrameContent.getText()); + creditEarned = new JLabel(creditEarned.getText()); + current_project_name_value = new JLabel(current_project_name_value.getText()); + current_project_duration_value = new JLabel(current_project_duration_value.getText()); + currrent_project_progression_value = new JLabel(currrent_project_progression_value.getText()); + current_project_compute_method_value = new JLabel(current_project_compute_method_value.getText()); + user_info_points_total_value = new JLabel(user_info_points_total_value.getText()); + renderable_projects_value = new JLabel(renderable_projects_value.getText()); + waiting_projects_value = new JLabel(waiting_projects_value.getText()); + connected_machines_value = new JLabel(connected_machines_value.getText()); + user_info_total_rendertime_this_session_value = new JLabel(user_info_total_rendertime_this_session_value.getText()); + lastRenderTime = new JLabel(lastRenderTime.getText()); + lastRender = new JLabel(lastRender.getText()); + + // set the new theme as the current one + currentTheme = UIManager.getLookAndFeel().getName(); + } + // current project JPanel current_project_panel = new JPanel(new SpringLayout()); current_project_panel.setBorder(BorderFactory.createTitledBorder("Project")); @@ -173,7 +203,7 @@ public class Working implements Activity { last_frame_panel.add(lastRenderTime); last_frame_panel.add(lastRender); - ImageIcon image = new ImageIcon(getClass().getResource("/title.png")); + ImageIcon image = new ImageIcon(getClass().getResource("/sheepit-logo.png")); JLabel labelImage = new JLabel(image); labelImage.setAlignmentX(Component.CENTER_ALIGNMENT); parent.getContentPane().add(labelImage); @@ -208,10 +238,12 @@ public class Working implements Activity { global_constraints.weightx = 1; global_constraints.gridx = 0; + parent.getContentPane().add(new JLabel(" "), global_constraints); // Add a separator between logo and first panel parent.getContentPane().add(current_project_panel, global_constraints); parent.getContentPane().add(global_stats_panel, global_constraints); parent.getContentPane().add(session_info_panel, global_constraints); parent.getContentPane().add(last_frame_panel, global_constraints); + parent.getContentPane().add(new JLabel(" "), global_constraints); // Add a separator between last panel and buttons parent.getContentPane().add(buttonsPanel, global_constraints); Spring widthLeftColumn = getBestWidth(current_project_panel, 4, 2);