feature: light/dark mode theme support (#208)
* feature: light/dark mode theme support New feature providing a more modern flat design with support for light and dark modes, including: - New SheepIt logo (old one removed) - New configuration option (-theme) that changes the existing client theme. - Minimal UI cosmetic changes (some separation lines under the logo and above the buttons)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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+"]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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<GPUDevice> 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?
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user