11package com .example .javafxtest ;
22
33import javafx .application .Application ;
4- import javafx .fxml .FXMLLoader ;
4+ import javafx .embed .swing .SwingFXUtils ;
5+ import javafx .geometry .Insets ;
56import 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 ;
717import 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
1124public 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