package com.dynamicwallpaper.gui; import com.dynamicwallpaper.core.NativeHelper; import com.dynamicwallpaper.core.WallpaperBuilder; import com.dynamicwallpaper.core.WallpaperInstaller; import com.dynamicwallpaper.model.WallpaperImage; import com.dynamicwallpaper.model.WallpaperMode; import com.dynamicwallpaper.util.ImageValidation; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SpinnerNumberModel; import javax.swing.SwingWorker; import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.io.File; import java.util.List; /** Top-level application window: mode switch, image editors, and the generate/install controls. */ public final class MainWindow extends JFrame { private final NativeHelper nativeHelper = new NativeHelper(); private final WallpaperBuilder wallpaperBuilder = new WallpaperBuilder(nativeHelper); private final JComboBox modeSelector = new JComboBox<>(WallpaperMode.values()); private final CardLayout cardLayout = new CardLayout(); private final JPanel modePanel = new JPanel(cardLayout); private final AppearancePanel appearancePanel = new AppearancePanel(); private final SolarPanel solarPanel = new SolarPanel(); private final JTextField outputPathField = new JTextField(); private final JSpinner qualitySpinner = new JSpinner(new SpinnerNumberModel(0.9, 0.1, 1.0, 0.05)); private final JButton generateButton = new JButton("Generate Wallpaper"); private final JButton setWallpaperButton = new JButton("Set as Desktop Picture"); private final JProgressBar progressBar = new JProgressBar(); private final JTextArea logArea = new JTextArea(8, 60); private File lastGeneratedFile; public MainWindow() { super("Dynamic Wallpaper Creator"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); add(buildHeader(), BorderLayout.NORTH); add(modePanel, BorderLayout.CENTER); add(buildFooter(), BorderLayout.SOUTH); modePanel.add(appearancePanel, WallpaperMode.APPEARANCE.name()); modePanel.add(solarPanel, WallpaperMode.SOLAR.name()); modeSelector.addActionListener(e -> cardLayout.show(modePanel, currentMode().name())); setWallpaperButton.setEnabled(false); setWallpaperButton.addActionListener(e -> setAsDesktopPicture()); generateButton.addActionListener(e -> generate()); setMinimumSize(new Dimension(760, 640)); pack(); setLocationRelativeTo(null); this.setSize(new Dimension(1200,800)); this.setLocationRelativeTo(null); // Center on screen } private Component buildHeader() { JPanel header = new JPanel(new BorderLayout(8, 8)); header.setBorder(BorderFactory.createEmptyBorder(12, 16, 0, 16)); JLabel title = new JLabel("Choose a wallpaper type:"); JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT)); left.add(title); left.add(modeSelector); JLabel modeDescription = new JLabel(currentMode().description()); modeSelector.addActionListener(e -> modeDescription.setText(currentMode().description())); modeDescription.setEnabled(false); header.add(left, BorderLayout.NORTH); header.add(modeDescription, BorderLayout.SOUTH); return header; } private Component buildFooter() { JPanel footer = new JPanel(); footer.setLayout(new BorderLayout(8, 8)); footer.setBorder(BorderFactory.createEmptyBorder(8, 16, 16, 16)); JPanel outputRow = new JPanel(new BorderLayout(8, 8)); outputRow.add(new JLabel("Output file:"), BorderLayout.WEST); outputPathField.setText(defaultOutputPath()); outputRow.add(outputPathField, BorderLayout.CENTER); JButton browseButton = new JButton("Choose…"); browseButton.addActionListener(e -> chooseOutputPath()); outputRow.add(browseButton, BorderLayout.EAST); JPanel controlsRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); controlsRow.add(new JLabel("Quality:")); qualitySpinner.setPreferredSize(new Dimension(70, qualitySpinner.getPreferredSize().height)); controlsRow.add(qualitySpinner); controlsRow.add(generateButton); controlsRow.add(setWallpaperButton); JPanel top = new JPanel(new GridLayout(2, 1)); top.add(outputRow); top.add(controlsRow); logArea.setEditable(false); logArea.setFont(new java.awt.Font(java.awt.Font.MONOSPACED, java.awt.Font.PLAIN, 12)); footer.add(top, BorderLayout.NORTH); footer.add(progressBar, BorderLayout.CENTER); footer.add(new JScrollPane(logArea), BorderLayout.SOUTH); return footer; } private WallpaperMode currentMode() { return (WallpaperMode) modeSelector.getSelectedItem(); } private String defaultOutputPath() { String home = System.getProperty("user.home"); return new File(home, "Desktop/MyDynamicWallpaper.heic").getAbsolutePath(); } private void chooseOutputPath() { JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle("Save Dynamic Wallpaper As"); chooser.setFileFilter(new FileNameExtensionFilter("HEIC Image", "heic")); chooser.setSelectedFile(new File(outputPathField.getText())); if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { File selected = chooser.getSelectedFile(); if (!selected.getName().toLowerCase().endsWith(".heic")) { selected = new File(selected.getParentFile(), selected.getName() + ".heic"); } outputPathField.setText(selected.getAbsolutePath()); } } private void generate() { File output = new File(outputPathField.getText().trim()); if (output.getParentFile() == null || !output.getParentFile().isDirectory()) { JOptionPane.showMessageDialog(this, "Choose a valid output location first.", "Missing output location", JOptionPane.WARNING_MESSAGE); return; } WallpaperMode mode = currentMode(); double quality = (Double) qualitySpinner.getValue(); if (mode == WallpaperMode.APPEARANCE) { File light = appearancePanel.lightImage(); File dark = appearancePanel.darkImage(); if (light == null || dark == null) { JOptionPane.showMessageDialog(this, "Choose both a light image and a dark image first.", "Missing images", JOptionPane.WARNING_MESSAGE); return; } String warning = ImageValidation.checkConsistentDimensions(List.of(light, dark)); if (warning != null && !confirmProceedDespiteWarning(warning)) { return; } runGenerateJob(output, listener -> wallpaperBuilder.buildAppearance(light, dark, output, quality, listener)); } else { List images = solarPanel.images(); if (images.size() < 2) { JOptionPane.showMessageDialog(this, "Add at least two images for a sun-position wallpaper.", "Not enough images", JOptionPane.WARNING_MESSAGE); return; } String warning = ImageValidation.checkConsistentDimensions(images.stream().map(WallpaperImage::file).toList()); if (warning != null && !confirmProceedDespiteWarning(warning)) { return; } runGenerateJob(output, listener -> wallpaperBuilder.buildSolar(images, output, quality, listener)); } } private boolean confirmProceedDespiteWarning(String warning) { int choice = JOptionPane.showConfirmDialog(this, warning + "\n\nContinue anyway?", "Image sizes differ", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); return choice == JOptionPane.YES_OPTION; } @FunctionalInterface private interface GenerateJob { void run(WallpaperBuilder.ProgressListener listener) throws WallpaperBuilder.BuildException; } private void runGenerateJob(File output, GenerateJob job) { setBusy(true); logArea.setText(""); setWallpaperButton.setEnabled(false); SwingWorker worker = new SwingWorker<>() { @Override protected Void doInBackground() throws Exception { job.run(line -> publish(line)); return null; } @Override protected void process(List chunks) { for (String line : chunks) { logArea.append(line); logArea.append("\n"); } } @Override protected void done() { setBusy(false); try { get(); logArea.append("Done. Saved to " + output.getAbsolutePath() + "\n"); lastGeneratedFile = output; setWallpaperButton.setEnabled(true); } catch (Exception e) { Throwable cause = e.getCause() != null ? e.getCause() : e; logArea.append("Failed: " + cause.getMessage() + "\n"); JOptionPane.showMessageDialog(MainWindow.this, cause.getMessage(), "Could not generate wallpaper", JOptionPane.ERROR_MESSAGE); } } }; worker.execute(); } private void setBusy(boolean busy) { generateButton.setEnabled(!busy); progressBar.setIndeterminate(busy); } private void setAsDesktopPicture() { if (lastGeneratedFile == null) { return; } SwingWorker worker = new SwingWorker<>() { @Override protected Void doInBackground() throws Exception { WallpaperInstaller.setAsDesktopPicture(lastGeneratedFile); return null; } @Override protected void done() { try { get(); logArea.append("Set as desktop picture.\n"); } catch (Exception e) { Throwable cause = e.getCause() != null ? e.getCause() : e; JOptionPane.showMessageDialog(MainWindow.this, cause.getMessage(), "Could not set desktop picture", JOptionPane.ERROR_MESSAGE); } } }; worker.execute(); } }