package com.mes.ui; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.TransferHandler; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class VisualUiEditorPanel extends JPanel { private static final int CANVAS_WIDTH = 990; private static final int CANVAS_HEIGHT = 550; private static final int GRID_SIZE = 8; private static final int HANDLE_SIZE = 10; private static final String DESIGN_FILE = "ui-designs/op40-main.ui.json"; private final File designFile; private final Container sourceContainer; private final Map sourceComponents = new HashMap(); private final JPanel canvas; private JSplitPane sideContent; private JButton sideToggleButton; private final JLabel statusLabel; private final Deque undoStack = new ArrayDeque(); private final Deque redoStack = new ArrayDeque(); private JTextField textField; private JTextField xField; private JTextField yField; private JTextField widthField; private JTextField heightField; private JSONObject schema; private String selectedId; private boolean dirty; public VisualUiEditorPanel() { this(null); } public VisualUiEditorPanel(Container sourceContainer) { this.sourceContainer = sourceContainer; this.designFile = new File(System.getProperty("user.dir"), DESIGN_FILE); this.schema = loadSchema(); if (sourceContainer != null) { indexSourceComponents(sourceContainer, sourceComponents); } setLayout(new BorderLayout(8, 8)); setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); JPanel palette = createPalette(); JPanel inspector = createInspector(); this.canvas = createCanvas(); this.sideContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT, palette, inspector); this.sideToggleButton = new JButton("<"); this.statusLabel = new JLabel("Ready"); sideContent.setResizeWeight(0.35); sideContent.setPreferredSize(new Dimension(250, 500)); add(canvas, BorderLayout.CENTER); add(statusLabel, BorderLayout.SOUTH); installShortcuts(); renderAll(); } private JPanel createPalette() { JPanel panel = new JPanel(new GridLayout(0, 1, 6, 6)); panel.setBorder(BorderFactory.createTitledBorder("Palette")); addPaletteButton(panel, "Label", "label"); addPaletteButton(panel, "Button", "button"); addPaletteButton(panel, "Text Field", "textfield"); addPaletteButton(panel, "Panel", "panel"); return panel; } private void addPaletteButton(JPanel panel, String title, final String type) { JButton button = new JButton(title); button.setFocusable(false); button.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { addComponentAt(type, 40 + schema.getJSONArray("components").size() * 16, 40 + schema.getJSONArray("components").size() * 16); } }); button.setTransferHandler(new TransferHandler("text") { protected java.awt.datatransfer.Transferable createTransferable(JComponent c) { return new StringSelection(type); } public int getSourceActions(JComponent c) { return COPY; } }); button.addMouseMotionListener(new MouseAdapter() { public void mouseDragged(MouseEvent e) { JComponent component = (JComponent) e.getSource(); component.getTransferHandler().exportAsDrag(component, e, TransferHandler.COPY); } }); panel.add(button); } public JPanel createExternalToolPanel() { final JPanel panel = new JPanel(new BorderLayout()); sideToggleButton.setFocusable(false); sideToggleButton.setPreferredSize(new Dimension(28, 40)); sideToggleButton.setToolTipText("Collapse palette"); sideToggleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { toggleSideBar(panel); } }); panel.add(sideToggleButton, BorderLayout.WEST); panel.add(sideContent, BorderLayout.CENTER); return panel; } private void toggleSideBar(JPanel panel) { boolean expanded = sideContent.getParent() == panel; if (expanded) { panel.remove(sideContent); sideToggleButton.setText(">"); sideToggleButton.setToolTipText("Expand palette"); panel.setPreferredSize(new Dimension(30, 500)); } else { panel.add(sideContent, BorderLayout.CENTER); sideToggleButton.setText("<"); sideToggleButton.setToolTipText("Collapse palette"); panel.setPreferredSize(null); } panel.revalidate(); panel.repaint(); java.awt.Window window = SwingUtilities.getWindowAncestor(panel); if (window != null) { window.pack(); } } private JPanel createInspector() { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.setBorder(BorderFactory.createTitledBorder("Properties")); textField = addProperty(panel, "Text"); xField = addProperty(panel, "X"); yField = addProperty(panel, "Y"); widthField = addProperty(panel, "Width"); heightField = addProperty(panel, "Height"); addButton(panel, "Apply", new Runnable() { public void run() { applyInspector(); } }); addButton(panel, "Delete", new Runnable() { public void run() { deleteSelected(); } }); addButton(panel, "Import Current", new Runnable() { public void run() { importCurrentLayout(); } }); addButton(panel, "Apply To Live", new Runnable() { public void run() { applyToSourceContainer(); } }); addButton(panel, "Save", new Runnable() { public void run() { saveDesign(); } }); return panel; } private void addButton(JPanel panel, String title, final Runnable runnable) { JButton button = new JButton(title); button.setAlignmentX(Component.LEFT_ALIGNMENT); button.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { runnable.run(); } }); panel.add(button); } private JTextField addProperty(JPanel panel, String label) { JLabel jLabel = new JLabel(label); jLabel.setAlignmentX(Component.LEFT_ALIGNMENT); JTextField field = new JTextField(); field.setMaximumSize(new Dimension(Integer.MAX_VALUE, 28)); field.setAlignmentX(Component.LEFT_ALIGNMENT); field.addFocusListener(new FocusAdapter() { public void focusLost(FocusEvent e) { applyInspector(); } }); panel.add(jLabel); panel.add(field); return field; } private JPanel createCanvas() { JPanel panel = new JPanel(null); panel.setBackground(new Color(245, 247, 250)); panel.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT)); panel.setBorder(BorderFactory.createLineBorder(new Color(180, 190, 200))); panel.setFocusable(true); panel.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { selectedId = null; updateInspector(); renderAll(); canvas.requestFocusInWindow(); } }); panel.setTransferHandler(new TransferHandler() { public boolean canImport(TransferSupport support) { return support.isDataFlavorSupported(DataFlavor.stringFlavor); } public boolean importData(TransferSupport support) { if (!canImport(support)) { return false; } try { String type = (String) support.getTransferable().getTransferData(DataFlavor.stringFlavor); Point point = support.getDropLocation().getDropPoint(); addComponentAt(type, point.x, point.y); return true; } catch (Exception e) { return false; } } }); return panel; } private void installShortcuts() { bindShortcut("save", KeyStroke.getKeyStroke("control S"), new Runnable() { public void run() { saveDesign(); } }); bindShortcut("undo", KeyStroke.getKeyStroke("control Z"), new Runnable() { public void run() { undo(); } }); bindShortcut("redo", KeyStroke.getKeyStroke("control Y"), new Runnable() { public void run() { redo(); } }); bindShortcut("redoShift", KeyStroke.getKeyStroke("control shift Z"), new Runnable() { public void run() { redo(); } }); bindShortcut("delete", KeyStroke.getKeyStroke("DELETE"), new Runnable() { public void run() { deleteSelected(); } }); } private void bindShortcut(String name, KeyStroke keyStroke, final Runnable action) { getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keyStroke, name); getActionMap().put(name, new javax.swing.AbstractAction() { public void actionPerformed(ActionEvent e) { if (!(java.awt.KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() instanceof JTextField)) { action.run(); } } }); } private JSONObject loadSchema() { if (designFile.exists()) { try { String text = new String(Files.readAllBytes(designFile.toPath()), StandardCharsets.UTF_8); JSONObject loaded = JSON.parseObject(text); if (loaded != null && loaded.getJSONArray("components") != null) { return loaded; } } catch (Exception e) { JOptionPane.showMessageDialog(this, "Failed to load UI design: " + e.getMessage()); } } return sourceContainer == null ? emptySchema() : schemaFromContainer(sourceContainer); } private JSONObject emptySchema() { JSONObject root = new JSONObject(); JSONObject canvasObject = new JSONObject(); canvasObject.put("width", CANVAS_WIDTH); canvasObject.put("height", CANVAS_HEIGHT); canvasObject.put("gridSize", GRID_SIZE); root.put("version", 1); root.put("source", "empty"); root.put("canvas", canvasObject); root.put("components", new JSONArray()); return root; } private JSONObject schemaFromContainer(Container container) { JSONObject root = emptySchema(); root.put("source", "runtime-swing-container"); JSONArray components = root.getJSONArray("components"); sourceComponents.clear(); Component[] children = container.getComponents(); for (int i = 0; i < children.length; i++) { Component child = children[i]; if (!(child instanceof JComponent) || !child.isVisible()) { continue; } JSONObject item = componentToJson((JComponent) child, i); components.add(item); sourceComponents.put(item.getString("sourceId"), child); } return root; } private JSONObject componentToJson(JComponent component, int index) { Rectangle bounds = component.getBounds(); String type = getComponentType(component); String sourceId = type + "-" + index; component.putClientProperty("visualUiEditor.sourceId", sourceId); JSONObject item = new JSONObject(); JSONObject props = new JSONObject(); item.put("id", sourceId); item.put("sourceId", sourceId); item.put("type", type); item.put("x", bounds.x); item.put("y", bounds.y); item.put("width", Math.max(bounds.width, 24)); item.put("height", Math.max(bounds.height, 24)); props.put("text", getComponentText(component)); props.put("enabled", component.isEnabled()); item.put("props", props); item.put("style", new JSONObject()); return item; } public static void applySavedDesign(Container container) { JSONObject savedSchema = readSavedSchema(); if (savedSchema == null || container == null) { return; } Map componentsBySourceId = new HashMap(); indexSourceComponents(container, componentsBySourceId); applySchemaToContainer(savedSchema, container, componentsBySourceId); } private static JSONObject readSavedSchema() { File file = new File(System.getProperty("user.dir"), DESIGN_FILE); if (!file.exists()) { return null; } try { String text = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); JSONObject loaded = JSON.parseObject(text); if (loaded != null && loaded.getJSONArray("components") != null) { return loaded; } } catch (Exception e) { System.err.println("Failed to load saved UI design: " + e.getMessage()); } return null; } private static void indexSourceComponents(Container container, Map target) { target.clear(); Component[] children = container.getComponents(); for (int i = 0; i < children.length; i++) { Component child = children[i]; if (!(child instanceof JComponent)) { continue; } JComponent component = (JComponent) child; String sourceId = (String) component.getClientProperty("visualUiEditor.sourceId"); if (sourceId == null || sourceId.length() == 0) { sourceId = getComponentType(component) + "-" + i; component.putClientProperty("visualUiEditor.sourceId", sourceId); } target.put(sourceId, component); } } private static void applySchemaToContainer(JSONObject schema, Container container, Map componentsBySourceId) { Set appliedSourceIds = new HashSet(); JSONArray components = schema.getJSONArray("components"); for (int i = 0; i < components.size(); i++) { JSONObject item = components.getJSONObject(i); String sourceId = item.getString("sourceId"); Component component = sourceId == null ? null : componentsBySourceId.get(sourceId); if (component == null) { String id = item.getString("id"); component = id == null ? null : componentsBySourceId.get(id); } if (component == null) { component = createRuntimeComponent(item); String newSourceId = item.getString("id"); if (newSourceId != null) { ((JComponent) component).putClientProperty("visualUiEditor.sourceId", newSourceId); componentsBySourceId.put(newSourceId, component); appliedSourceIds.add(newSourceId); } container.add(component); } else { String existingSourceId = (String) ((JComponent) component).getClientProperty("visualUiEditor.sourceId"); if (existingSourceId != null) { appliedSourceIds.add(existingSourceId); } } component.setVisible(true); component.setBounds(item.getIntValue("x"), item.getIntValue("y"), item.getIntValue("width"), item.getIntValue("height")); applyText(component, item.getJSONObject("props").getString("text")); } for (Map.Entry entry : componentsBySourceId.entrySet()) { if (!appliedSourceIds.contains(entry.getKey())) { entry.getValue().setVisible(false); } } container.revalidate(); container.repaint(); } private static JComponent createRuntimeComponent(JSONObject item) { String type = item.getString("type"); String text = item.getJSONObject("props").getString("text"); if ("button".equals(type)) { return new JButton(text); } if ("textfield".equals(type)) { return new JTextField(text); } if ("panel".equals(type)) { JPanel panel = new JPanel(new BorderLayout()); panel.add(new JLabel(text, SwingConstants.CENTER), BorderLayout.CENTER); panel.setBackground(new Color(230, 235, 240)); return panel; } JLabel label = new JLabel(text, SwingConstants.CENTER); label.setOpaque(true); label.setBackground(Color.WHITE); return label; } private static String getComponentType(JComponent component) { if (component instanceof JButton) { return "button"; } if (component instanceof JTextField) { return "textfield"; } if (component instanceof JLabel) { return "label"; } if (component instanceof JPanel) { return "panel"; } return "component"; } private static String getComponentText(JComponent component) { if (component instanceof JButton) { return ((JButton) component).getText(); } if (component instanceof JTextField) { return ((JTextField) component).getText(); } if (component instanceof JLabel) { return ((JLabel) component).getText(); } return component.getClass().getSimpleName(); } private void saveDesign() { try { File parent = designFile.getParentFile(); if (parent != null && !parent.exists()) { parent.mkdirs(); } Files.write(designFile.toPath(), JSON.toJSONString(schema).getBytes(StandardCharsets.UTF_8)); dirty = false; applyToSourceContainer(); updateStatus("Saved and applied " + designFile.getPath()); } catch (IOException e) { JOptionPane.showMessageDialog(this, "Save failed: " + e.getMessage()); } } private void importCurrentLayout() { if (sourceContainer == null) { return; } pushHistory(); schema = schemaFromContainer(sourceContainer); selectedId = null; markDirty(); renderAll(); } private void applyToSourceContainer() { if (sourceContainer == null) { return; } indexSourceComponents(sourceContainer, sourceComponents); applySchemaToContainer(schema, sourceContainer, sourceComponents); updateStatus("Applied to current 工作面板 instance."); } private static void applyText(Component component, String text) { if (component instanceof JButton) { ((JButton) component).setText(text); } else if (component instanceof JTextField) { ((JTextField) component).setText(text); } else if (component instanceof JLabel) { ((JLabel) component).setText(text); } } private void addComponentAt(String type, int x, int y) { pushHistory(); JSONObject item = new JSONObject(); JSONObject props = new JSONObject(); String id = type + "-" + System.currentTimeMillis(); int width = defaultWidth(type); int height = defaultHeight(type); item.put("id", id); item.put("type", type); item.put("x", clamp(snap(x), 0, CANVAS_WIDTH - width)); item.put("y", clamp(snap(y), 0, CANVAS_HEIGHT - height)); item.put("width", width); item.put("height", height); props.put("text", defaultText(type)); props.put("enabled", true); item.put("props", props); item.put("style", new JSONObject()); schema.getJSONArray("components").add(item); selectedId = id; markDirty(); renderAll(); } private int defaultWidth(String type) { return "panel".equals(type) || "textfield".equals(type) ? 180 : 120; } private int defaultHeight(String type) { return "panel".equals(type) ? 120 : 36; } private String defaultText(String type) { if ("label".equals(type)) { return "Label"; } if ("button".equals(type)) { return "Button"; } if ("textfield".equals(type)) { return "Text"; } return "Panel"; } private void renderAll() { renderCanvas(canvas, true); updateInspector(); updateStatus(null); } private void renderCanvas(JPanel target, boolean editable) { target.removeAll(); JSONArray components = schema.getJSONArray("components"); for (int i = 0; i < components.size(); i++) { JSONObject item = components.getJSONObject(i); JComponent view = createView(item, editable); view.setBounds(item.getIntValue("x"), item.getIntValue("y"), item.getIntValue("width"), item.getIntValue("height")); target.add(view); } target.revalidate(); target.repaint(); } private JComponent createView(final JSONObject item, boolean editable) { String type = item.getString("type"); String text = item.getJSONObject("props").getString("text"); JComponent view; if ("button".equals(type)) { view = new DesignerButton(text, editable && selectedId != null && selectedId.equals(item.getString("id"))); } else if ("textfield".equals(type)) { JTextField field = new DesignerTextField(text, editable && selectedId != null && selectedId.equals(item.getString("id"))); field.setEditable(false); view = field; } else if ("panel".equals(type)) { JPanel panel = new DesignerPanel(editable && selectedId != null && selectedId.equals(item.getString("id"))); panel.setLayout(new BorderLayout()); panel.add(new JLabel(text, SwingConstants.CENTER), BorderLayout.CENTER); panel.setBackground(new Color(230, 235, 240)); view = panel; } else { JLabel label = new DesignerLabel(text, editable && selectedId != null && selectedId.equals(item.getString("id"))); label.setOpaque(true); label.setBackground(Color.WHITE); view = label; } view.setFont(new Font("Dialog", Font.PLAIN, 16)); view.setEnabled(editable || item.getJSONObject("props").getBooleanValue("enabled")); view.setBorder(BorderFactory.createLineBorder(selectedId != null && selectedId.equals(item.getString("id")) && editable ? Color.BLUE : Color.GRAY, editable ? 2 : 1)); if (editable) { installComponentEditing(view, item); } return view; } private void installComponentEditing(final JComponent view, final JSONObject item) { final String id = item.getString("id"); MouseAdapter adapter = new MouseAdapter() { private Point startPoint; private Rectangle startBounds; private String resizeHandle; private boolean changed; public void mousePressed(MouseEvent e) { e.consume(); selectedId = id; startPoint = SwingUtilities.convertPoint(view, e.getPoint(), canvas); startBounds = view.getBounds(); resizeHandle = getResizeHandle(e.getPoint(), view); changed = false; pushHistory(); updateInspector(); view.setBorder(BorderFactory.createLineBorder(Color.BLUE, 2)); canvas.repaint(); canvas.requestFocusInWindow(); } public void mouseDragged(MouseEvent e) { Point current = SwingUtilities.convertPoint(view, e.getPoint(), canvas); int dx = current.x - startPoint.x; int dy = current.y - startPoint.y; int nextX = startBounds.x; int nextY = startBounds.y; int nextWidth = startBounds.width; int nextHeight = startBounds.height; if (resizeHandle != null) { Rectangle resized = resizeBounds(startBounds, dx, dy, resizeHandle); nextX = resized.x; nextY = resized.y; nextWidth = resized.width; nextHeight = resized.height; } else { nextX = clamp(snap(startBounds.x + dx), 0, CANVAS_WIDTH - startBounds.width); nextY = clamp(snap(startBounds.y + dy), 0, CANVAS_HEIGHT - startBounds.height); } item.put("x", nextX); item.put("y", nextY); item.put("width", nextWidth); item.put("height", nextHeight); view.setBounds(nextX, nextY, nextWidth, nextHeight); changed = true; markDirty(); updateInspector(); canvas.repaint(); } public void mouseReleased(MouseEvent e) { if (!changed && !undoStack.isEmpty()) { undoStack.pop(); } else if (changed) { renderAll(); } } public void mouseMoved(MouseEvent e) { view.setCursor(cursorForHandle(getResizeHandle(e.getPoint(), view))); } }; view.addMouseListener(adapter); view.addMouseMotionListener(adapter); } private String getResizeHandle(Point point, JComponent component) { int w = component.getWidth(); int h = component.getHeight(); boolean left = point.x <= HANDLE_SIZE; boolean right = point.x >= w - HANDLE_SIZE; boolean top = point.y <= HANDLE_SIZE; boolean bottom = point.y >= h - HANDLE_SIZE; boolean middleX = point.x >= (w / 2) - HANDLE_SIZE && point.x <= (w / 2) + HANDLE_SIZE; boolean middleY = point.y >= (h / 2) - HANDLE_SIZE && point.y <= (h / 2) + HANDLE_SIZE; if (left && top) { return "nw"; } if (right && top) { return "ne"; } if (left && bottom) { return "sw"; } if (right && bottom) { return "se"; } if (middleX && top) { return "n"; } if (middleX && bottom) { return "s"; } if (left && middleY) { return "w"; } if (right && middleY) { return "e"; } return null; } private Rectangle resizeBounds(Rectangle startBounds, int dx, int dy, String handle) { int minSize = 24; int x = startBounds.x; int y = startBounds.y; int width = startBounds.width; int height = startBounds.height; if (handle.indexOf("e") >= 0) { width = clamp(snap(startBounds.width + dx), minSize, CANVAS_WIDTH - startBounds.x); } if (handle.indexOf("s") >= 0) { height = clamp(snap(startBounds.height + dy), minSize, CANVAS_HEIGHT - startBounds.y); } if (handle.indexOf("w") >= 0) { int newX = clamp(snap(startBounds.x + dx), 0, startBounds.x + startBounds.width - minSize); width = startBounds.x + startBounds.width - newX; x = newX; } if (handle.indexOf("n") >= 0) { int newY = clamp(snap(startBounds.y + dy), 0, startBounds.y + startBounds.height - minSize); height = startBounds.y + startBounds.height - newY; y = newY; } return new Rectangle(x, y, width, height); } private Cursor cursorForHandle(String handle) { if (handle == null) { return Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); } if ("n".equals(handle) || "s".equals(handle)) { return Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR); } if ("e".equals(handle) || "w".equals(handle)) { return Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); } if ("ne".equals(handle) || "sw".equals(handle)) { return Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR); } return Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR); } private int snap(int value) { return Math.round(value / (float) GRID_SIZE) * GRID_SIZE; } private int clamp(int value, int min, int max) { return Math.max(min, Math.min(max, value)); } private void applyInspector() { JSONObject item = selectedItem(); if (item == null) { return; } pushHistory(); try { item.getJSONObject("props").put("text", textField.getText()); item.put("x", clamp(snap(Integer.parseInt(xField.getText())), 0, CANVAS_WIDTH)); item.put("y", clamp(snap(Integer.parseInt(yField.getText())), 0, CANVAS_HEIGHT)); item.put("width", clamp(snap(Integer.parseInt(widthField.getText())), 24, CANVAS_WIDTH)); item.put("height", clamp(snap(Integer.parseInt(heightField.getText())), 24, CANVAS_HEIGHT)); markDirty(); renderAll(); } catch (NumberFormatException e) { if (!undoStack.isEmpty()) { undoStack.pop(); } JOptionPane.showMessageDialog(this, "Position and size must be numbers."); updateInspector(); } } private void deleteSelected() { if (selectedId == null) { return; } pushHistory(); JSONArray components = schema.getJSONArray("components"); for (int i = components.size() - 1; i >= 0; i--) { if (selectedId.equals(components.getJSONObject(i).getString("id"))) { components.remove(i); } } selectedId = null; markDirty(); renderAll(); } private JSONObject selectedItem() { if (selectedId == null) { return null; } JSONArray components = schema.getJSONArray("components"); for (int i = 0; i < components.size(); i++) { JSONObject item = components.getJSONObject(i); if (selectedId.equals(item.getString("id"))) { return item; } } return null; } private void updateInspector() { JSONObject item = selectedItem(); boolean enabled = item != null; textField.setEnabled(enabled); xField.setEnabled(enabled); yField.setEnabled(enabled); widthField.setEnabled(enabled); heightField.setEnabled(enabled); if (!enabled) { textField.setText(""); xField.setText(""); yField.setText(""); widthField.setText(""); heightField.setText(""); return; } textField.setText(item.getJSONObject("props").getString("text")); xField.setText(String.valueOf(item.getIntValue("x"))); yField.setText(String.valueOf(item.getIntValue("y"))); widthField.setText(String.valueOf(item.getIntValue("width"))); heightField.setText(String.valueOf(item.getIntValue("height"))); } private void pushHistory() { undoStack.push(JSON.toJSONString(schema)); redoStack.clear(); } private void undo() { if (undoStack.isEmpty()) { return; } redoStack.push(JSON.toJSONString(schema)); schema = JSON.parseObject(undoStack.pop()); selectedId = null; markDirty(); renderAll(); } private void redo() { if (redoStack.isEmpty()) { return; } undoStack.push(JSON.toJSONString(schema)); schema = JSON.parseObject(redoStack.pop()); selectedId = null; markDirty(); renderAll(); } private void markDirty() { dirty = true; } private void updateStatus(String message) { if (message != null) { statusLabel.setText(message); return; } statusLabel.setText((dirty ? "Unsaved" : "Saved") + " - imported current 工作面板. Ctrl+S save, Ctrl+Z undo, Ctrl+Y redo. File: " + DESIGN_FILE); } private static void paintHandles(Component component, Graphics graphics, boolean selected) { if (!selected) { return; } graphics.setColor(Color.BLUE); int s = HANDLE_SIZE; int half = s / 2; int w = component.getWidth(); int h = component.getHeight(); int[][] points = new int[][]{ {0, 0}, {w / 2, 0}, {w, 0}, {0, h / 2}, {w, h / 2}, {0, h}, {w / 2, h}, {w, h} }; for (int i = 0; i < points.length; i++) { graphics.fillRect(points[i][0] - half, points[i][1] - half, s, s); } } private static class DesignerButton extends JButton { private final boolean selected; DesignerButton(String text, boolean selected) { super(text); this.selected = selected; } protected void paintComponent(Graphics graphics) { super.paintComponent(graphics); paintHandles(this, graphics, selected); } } private static class DesignerTextField extends JTextField { private final boolean selected; DesignerTextField(String text, boolean selected) { super(text); this.selected = selected; } protected void paintComponent(Graphics graphics) { super.paintComponent(graphics); paintHandles(this, graphics, selected); } } private static class DesignerLabel extends JLabel { private final boolean selected; DesignerLabel(String text, boolean selected) { super(text, SwingConstants.CENTER); this.selected = selected; } protected void paintComponent(Graphics graphics) { super.paintComponent(graphics); paintHandles(this, graphics, selected); } } private static class DesignerPanel extends JPanel { private final boolean selected; DesignerPanel(boolean selected) { this.selected = selected; } protected void paintComponent(Graphics graphics) { super.paintComponent(graphics); paintHandles(this, graphics, selected); } } }