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:
Luis Uguina
2020-04-14 23:24:28 +10:00
committed by GitHub
parent 4f5fcf9d1b
commit b2227e9f4f
9 changed files with 188 additions and 14 deletions

View File

@@ -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 {

BIN
resources/sheepit-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -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;
}

View File

@@ -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+"]";
}
}

View File

@@ -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) {

View File

@@ -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.");

View File

@@ -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?

View File

@@ -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);