Skip to content

Commit 0085f54

Browse files
authored
Refactor ShapeDrawingApp with new features
Refactor ShapeDrawingApp to enhance functionality and UI. Added canvas resizing, dark mode toggle, and improved undo/redo capabilities.
1 parent 555ae9d commit 0085f54

1 file changed

Lines changed: 342 additions & 18 deletions

File tree

Lines changed: 342 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,360 @@
11
package com.example.javafxtest;
22

33
import javafx.application.Application;
4-
import javafx.fxml.FXMLLoader;
4+
import javafx.embed.swing.SwingFXUtils;
5+
import javafx.geometry.Insets;
56
import javafx.scene.Scene;
6-
import javafx.scene.image.Image;
7+
import javafx.scene.SnapshotParameters;
8+
import javafx.scene.canvas.Canvas;
9+
import javafx.scene.canvas.GraphicsContext;
10+
import javafx.scene.control.*;
11+
import javafx.scene.image.WritableImage;
12+
import javafx.scene.input.MouseEvent;
13+
import javafx.scene.layout.BorderPane;
14+
import javafx.scene.layout.StackPane;
15+
import javafx.scene.paint.Color;
16+
import javafx.stage.FileChooser;
717
import javafx.stage.Stage;
818

9-
19+
import javax.imageio.ImageIO;
20+
import java.io.File;
21+
import java.util.ArrayDeque;
22+
import java.util.Deque;
1023

1124
public class ShapeDrawingApp extends Application {
25+
26+
private Canvas drawingCanvas;
27+
private Canvas previewCanvas;
28+
private GraphicsContext gc;
29+
private GraphicsContext previewGc;
30+
31+
private double startX;
32+
private double startY;
33+
34+
private final Deque<WritableImage> undoStack = new ArrayDeque<>();
35+
36+
private ColorPicker colorPicker;
37+
private Slider brushSlider;
38+
private ComboBox<String> modeBox;
39+
40+
private boolean darkMode = false;
41+
1242
@Override
13-
public void start(Stage stage) throws Exception {
14-
FXMLLoader loader = new FXMLLoader(getClass().getResource("AdvanceDrawing.fxml"));
15-
Scene scene = new Scene(loader.load(), 1000, 700);
16-
17-
// Enable Undo/Redo shortcuts
18-
scene.setOnKeyPressed(e -> {
19-
ShapeDrawingAppController controller = loader.getController();
20-
if (e.isControlDown() && e.getCode().toString().equals("Z")) controller.undo();
21-
if (e.isControlDown() && e.getCode().toString().equals("Y")) controller.redo();
22-
if (e.isControlDown() && e.getCode().toString().equals("S")) controller.saveCanvas();
23-
});
43+
public void start(Stage stage) {
44+
drawingCanvas = new Canvas(1200, 700);
45+
previewCanvas = new Canvas(1200, 700);
46+
47+
gc = drawingCanvas.getGraphicsContext2D();
48+
previewGc = previewCanvas.getGraphicsContext2D();
49+
50+
initializeCanvas();
51+
52+
StackPane canvasLayer = new StackPane(drawingCanvas, previewCanvas);
53+
canvasLayer.setPadding(new Insets(20));
54+
canvasLayer.setStyle("-fx-background-color: linear-gradient(to bottom right, #eef2ff, #f8fafc);");
2455

25-
Image icon = new Image("Mainlogo.png");
26-
stage.getIcons().add(icon);
56+
ScrollPane scrollPane = new ScrollPane(canvasLayer);
57+
scrollPane.setFitToWidth(true);
58+
scrollPane.setFitToHeight(true);
2759

28-
stage.setTitle("CanvasAdvance");
60+
BorderPane root = new BorderPane();
61+
root.setTop(createToolbar(stage));
62+
root.setCenter(scrollPane);
63+
root.setStyle("-fx-background-color: #f8fafc;");
64+
65+
setupMouseActions();
66+
67+
Scene scene = new Scene(root, 1250, 780);
68+
stage.setTitle("AdvanceDrawing - Professional JavaFX Drawing App");
2969
stage.setScene(scene);
3070
stage.show();
3171
}
3272

73+
private ToolBar createToolbar(Stage stage) {
74+
colorPicker = new ColorPicker(Color.web("#2563eb"));
75+
76+
brushSlider = new Slider(1, 50, 6);
77+
brushSlider.setShowTickLabels(true);
78+
brushSlider.setShowTickMarks(true);
79+
brushSlider.setMajorTickUnit(10);
80+
brushSlider.setPrefWidth(180);
81+
82+
modeBox = new ComboBox<>();
83+
modeBox.getItems().addAll("Pen", "Line", "Rectangle", "Circle", "Eraser");
84+
modeBox.setValue("Pen");
85+
modeBox.setPrefWidth(130);
86+
87+
Button undoButton = new Button("Undo");
88+
Button clearButton = new Button("Clear");
89+
Button saveButton = new Button("Save PNG");
90+
Button resizeButton = new Button("Resize Canvas");
91+
Button themeButton = new Button("Dark Mode");
92+
93+
undoButton.setOnAction(e -> undo());
94+
clearButton.setOnAction(e -> clearCanvas());
95+
saveButton.setOnAction(e -> saveCanvas(stage));
96+
resizeButton.setOnAction(e -> resizeCanvas());
97+
themeButton.setOnAction(e -> toggleTheme(themeButton));
98+
99+
ToolBar toolbar = new ToolBar(
100+
new Label("Color"),
101+
colorPicker,
102+
new Separator(),
103+
new Label("Brush"),
104+
brushSlider,
105+
new Separator(),
106+
new Label("Mode"),
107+
modeBox,
108+
new Separator(),
109+
undoButton,
110+
clearButton,
111+
saveButton,
112+
resizeButton,
113+
themeButton
114+
);
115+
116+
toolbar.setPadding(new Insets(12));
117+
toolbar.setStyle(
118+
"-fx-background-color: linear-gradient(to right, #0f172a, #1e293b);" +
119+
"-fx-border-color: #334155;" +
120+
"-fx-border-width: 0 0 1 0;"
121+
);
122+
123+
for (var node : toolbar.getItems()) {
124+
if (node instanceof Label label) {
125+
label.setTextFill(Color.WHITE);
126+
label.setStyle("-fx-font-weight: bold;");
127+
}
128+
if (node instanceof Button button) {
129+
button.setStyle(
130+
"-fx-background-color: #2563eb;" +
131+
"-fx-text-fill: white;" +
132+
"-fx-font-weight: bold;" +
133+
"-fx-background-radius: 8;" +
134+
"-fx-padding: 7 14;"
135+
);
136+
}
137+
}
138+
139+
return toolbar;
140+
}
141+
142+
private void initializeCanvas() {
143+
gc.setFill(Color.WHITE);
144+
gc.fillRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
145+
drawGrid();
146+
}
147+
148+
private void drawGrid() {
149+
gc.setStroke(Color.web("#e5e7eb"));
150+
gc.setLineWidth(0.5);
151+
152+
for (int x = 0; x < drawingCanvas.getWidth(); x += 25) {
153+
gc.strokeLine(x, 0, x, drawingCanvas.getHeight());
154+
}
155+
156+
for (int y = 0; y < drawingCanvas.getHeight(); y += 25) {
157+
gc.strokeLine(0, y, drawingCanvas.getWidth(), y);
158+
}
159+
}
160+
161+
private void setupMouseActions() {
162+
previewCanvas.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
163+
saveState();
164+
165+
startX = event.getX();
166+
startY = event.getY();
167+
168+
if (getMode().equals("Pen") || getMode().equals("Eraser")) {
169+
prepareGraphics(gc);
170+
gc.beginPath();
171+
gc.moveTo(startX, startY);
172+
gc.stroke();
173+
}
174+
});
175+
176+
previewCanvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {
177+
String mode = getMode();
178+
179+
if (mode.equals("Pen") || mode.equals("Eraser")) {
180+
prepareGraphics(gc);
181+
gc.lineTo(event.getX(), event.getY());
182+
gc.stroke();
183+
} else {
184+
drawPreview(event.getX(), event.getY());
185+
}
186+
});
187+
188+
previewCanvas.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
189+
String mode = getMode();
190+
191+
if (!mode.equals("Pen") && !mode.equals("Eraser")) {
192+
clearPreview();
193+
drawShape(gc, mode, startX, startY, event.getX(), event.getY());
194+
}
195+
});
196+
}
197+
198+
private void prepareGraphics(GraphicsContext graphics) {
199+
if (getMode().equals("Eraser")) {
200+
graphics.setStroke(Color.WHITE);
201+
graphics.setLineWidth(brushSlider.getValue() * 2);
202+
} else {
203+
graphics.setStroke(colorPicker.getValue());
204+
graphics.setLineWidth(brushSlider.getValue());
205+
}
206+
207+
graphics.setLineCap(javafx.scene.shape.StrokeLineCap.ROUND);
208+
graphics.setLineJoin(javafx.scene.shape.StrokeLineJoin.ROUND);
209+
}
210+
211+
private void drawPreview(double endX, double endY) {
212+
clearPreview();
213+
prepareGraphics(previewGc);
214+
drawShape(previewGc, getMode(), startX, startY, endX, endY);
215+
}
216+
217+
private void drawShape(GraphicsContext graphics, String mode, double x1, double y1, double x2, double y2) {
218+
prepareGraphics(graphics);
219+
220+
double x = Math.min(x1, x2);
221+
double y = Math.min(y1, y2);
222+
double width = Math.abs(x2 - x1);
223+
double height = Math.abs(y2 - y1);
224+
225+
switch (mode) {
226+
case "Line":
227+
graphics.strokeLine(x1, y1, x2, y2);
228+
break;
229+
230+
case "Rectangle":
231+
graphics.strokeRoundRect(x, y, width, height, 12, 12);
232+
break;
233+
234+
case "Circle":
235+
double size = Math.max(width, height);
236+
graphics.strokeOval(x, y, size, size);
237+
break;
238+
239+
default:
240+
break;
241+
}
242+
}
243+
244+
private void clearPreview() {
245+
previewGc.clearRect(0, 0, previewCanvas.getWidth(), previewCanvas.getHeight());
246+
}
247+
248+
private String getMode() {
249+
return modeBox.getValue();
250+
}
251+
252+
private void saveState() {
253+
WritableImage snapshot = drawingCanvas.snapshot(new SnapshotParameters(), null);
254+
undoStack.push(snapshot);
255+
256+
if (undoStack.size() > 20) {
257+
undoStack.removeLast();
258+
}
259+
}
260+
261+
private void undo() {
262+
if (!undoStack.isEmpty()) {
263+
WritableImage previous = undoStack.pop();
264+
gc.drawImage(previous, 0, 0);
265+
}
266+
}
267+
268+
private void clearCanvas() {
269+
saveState();
270+
gc.setFill(Color.WHITE);
271+
gc.fillRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
272+
drawGrid();
273+
}
274+
275+
private void saveCanvas(Stage stage) {
276+
FileChooser fileChooser = new FileChooser();
277+
fileChooser.setTitle("Save Drawing");
278+
fileChooser.getExtensionFilters().add(
279+
new FileChooser.ExtensionFilter("PNG Image", "*.png")
280+
);
281+
fileChooser.setInitialFileName("advanced-drawing.png");
282+
283+
File file = fileChooser.showSaveDialog(stage);
284+
285+
if (file != null) {
286+
try {
287+
WritableImage image = drawingCanvas.snapshot(new SnapshotParameters(), null);
288+
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);
289+
showAlert("Success", "Drawing saved successfully.");
290+
} catch (Exception e) {
291+
showAlert("Error", "Could not save image: " + e.getMessage());
292+
}
293+
}
294+
}
295+
296+
private void resizeCanvas() {
297+
TextInputDialog dialog = new TextInputDialog("1200x700");
298+
dialog.setTitle("Resize Canvas");
299+
dialog.setHeaderText("Enter new canvas size");
300+
dialog.setContentText("Format: widthxheight");
301+
302+
dialog.showAndWait().ifPresent(input -> {
303+
try {
304+
String[] parts = input.toLowerCase().split("x");
305+
double newWidth = Double.parseDouble(parts[0].trim());
306+
double newHeight = Double.parseDouble(parts[1].trim());
307+
308+
if (newWidth < 300 || newHeight < 300) {
309+
showAlert("Invalid size", "Width and height must be at least 300.");
310+
return;
311+
}
312+
313+
saveState();
314+
315+
WritableImage oldImage = drawingCanvas.snapshot(new SnapshotParameters(), null);
316+
317+
drawingCanvas.setWidth(newWidth);
318+
drawingCanvas.setHeight(newHeight);
319+
previewCanvas.setWidth(newWidth);
320+
previewCanvas.setHeight(newHeight);
321+
322+
gc.setFill(Color.WHITE);
323+
gc.fillRect(0, 0, newWidth, newHeight);
324+
drawGrid();
325+
gc.drawImage(oldImage, 0, 0);
326+
327+
} catch (Exception e) {
328+
showAlert("Invalid format", "Please use format like 1200x700.");
329+
}
330+
});
331+
}
332+
333+
private void toggleTheme(Button themeButton) {
334+
darkMode = !darkMode;
335+
336+
if (darkMode) {
337+
themeButton.setText("Light Mode");
338+
drawingCanvas.getParent().setStyle(
339+
"-fx-background-color: linear-gradient(to bottom right, #020617, #111827);"
340+
);
341+
} else {
342+
themeButton.setText("Dark Mode");
343+
drawingCanvas.getParent().setStyle(
344+
"-fx-background-color: linear-gradient(to bottom right, #eef2ff, #f8fafc);"
345+
);
346+
}
347+
}
348+
349+
private void showAlert(String title, String message) {
350+
Alert alert = new Alert(Alert.AlertType.INFORMATION);
351+
alert.setTitle(title);
352+
alert.setHeaderText(null);
353+
alert.setContentText(message);
354+
alert.showAndWait();
355+
}
356+
33357
public static void main(String[] args) {
34358
launch(args);
35359
}
36-
}
360+
}

0 commit comments

Comments
 (0)