Libraryless. Click here for Pure Java version (20367L/129K).
1 | sclass JPaintTool > MetaWithChangeListeners is Swingable, AutoCloseable { |
2 | settableWithVar File autoPersistFile; |
3 | settableWithVar double autoPersistInterval = 10.0; |
4 | settableWithVar Color brushColor = Color.black; |
5 | settableWithVar bool drawingAllowed = true; |
6 | settable Color bgColor = Color.lightGray; // background color around image surface |
7 | |
8 | event imageChanged(BufferedImage image); |
9 | |
10 | transient settableWithChange volatile bool dirty; |
11 | transient gettable BufferedImage image; |
12 | transient gettable ImageSurface imageSurface; |
13 | transient bool autoPersistInstalled; |
14 | transient ReliableSingleThread rstPersist = new(r _persist); |
15 | transient JPanel buttons; |
16 | |
17 | // tools |
18 | transient new ImageSurfaceScribbleTool scribbler; |
19 | transient new ImageSurfaceDragger dragger; |
20 | transient new ImageSurfaceSelector selector; |
21 | |
22 | transient JComboBox<WidthAndHeight> cbImageSize; |
23 | //transient settableWithVar S status; |
24 | transient new ButtonGroup colorButtonGroup; |
25 | transient new ButtonGroup toolSelectButtonGroup; |
26 | |
27 | transient simplyCached void init() { |
28 | imageSurface = makeImageSurface(); |
29 | |
30 | imageSurface.onUserModifiedImage(image -> { |
31 | print("userModifiedImage"); |
32 | setImage(image); |
33 | fireImageChanged(); |
34 | }); |
35 | |
36 | cbImageSize = jTypedComboBox(imageSizes()); |
37 | main onChange(cbImageSize, l1 setImageSize); |
38 | |
39 | printVars("JPaintTool.init", +image, +autoPersistFile); |
40 | if (image == null) { |
41 | setImage(makeNewImage()); |
42 | |
43 | if (autoPersistFile != null) { |
44 | pcall { |
45 | var img = loadImage2(autoPersistFile); |
46 | if (img != null) { |
47 | setImage(img); |
48 | print("Image loaded"); |
49 | } |
50 | } |
51 | |
52 | if (!autoPersistInstalled) { |
53 | set autoPersistInstalled; |
54 | awtEvery(imageSurface, autoPersistInterval, rstPersist); |
55 | bindToComponent(imageSurface, null, rstPersist); |
56 | } |
57 | } |
58 | } |
59 | |
60 | varDrawingAllowed().onChangeAndNow(b -> scribbler.enabled(print(scribblerEnabled := b))); |
61 | } |
62 | |
63 | BufferedImage makeNewImage() { |
64 | ret main newImage(defaultImageSize()); |
65 | } |
66 | |
67 | WidthAndHeight defaultImageSize() { ret getSelected(cbImageSize); } |
68 | |
69 | cachedVisualize { |
70 | init(); |
71 | var filePathLabel = new JFilePathLabel(autoPersistFile).nameOnly(); |
72 | varAutoPersistFile().onChange(file -> filePathLabel.setFile(file)); |
73 | |
74 | var filePathLabelVis = filePathLabel.visualize(); |
75 | componentPopupMenuItem(filePathLabel.button.visualize(), |
76 | "Save image now", r { set dirty; rstPersist!; }); |
77 | |
78 | ret withTopMargin(northCenterAndSouthWithMargin( |
79 | // north buttons |
80 | |
81 | buttons = jcenteredline(flattenToList( |
82 | jbutton("New image", rThread newImage), |
83 | withLabel("Size:", cbImageSize), |
84 | map colorSelectButton(colors()), |
85 | swing(-> new JImageSwitch(#1103077, #1103078, varDrawingAllowed()) |
86 | .setToolTips( |
87 | "Drawing allowed (click to change)", |
88 | "Drawing not allowed (click to change)")), |
89 | )), |
90 | |
91 | // image surface |
92 | |
93 | centerAndEastWithMargin( |
94 | jscroll_borderless(setBackground(bgColor, jFullCenter(imageSurface))), |
95 | //jscroll_center_borderless(imageSurface), |
96 | vstackWithSpacing( |
97 | makeToolSelectors() |
98 | ) |
99 | ), |
100 | |
101 | // south controls |
102 | centerAndEast( |
103 | withLeftMargin(bottomLeftControls()), |
104 | autoPersistFile == null ? jpanel() |
105 | : withSideMargin(withLabel("Image:", filePathLabelVis)) |
106 | ) |
107 | )); |
108 | } |
109 | |
110 | void setImage(BufferedImage image) { |
111 | this.image = image; |
112 | imageSurface.setImage(image); |
113 | imageChanged(image); |
114 | } |
115 | |
116 | transient swappable ImageSurface makeImageSurface() { |
117 | var is = pixelatedImageSurface(); |
118 | //is.setAutoZoomToDisplay(true); |
119 | is.specialPurposed = true; |
120 | is.allowPaste(true); |
121 | is.zoomable(false); |
122 | is.removeAllTools(); |
123 | //scribbler.onScribbleDone(scribble -> printStruct(scribble)); |
124 | scribbler.onPainted(l0 fireImageChanged); |
125 | varBrushColor().onChangeAndNow(color -> scribbler.setColor(color)); |
126 | is.setTool(scribbler); |
127 | |
128 | ret is; |
129 | } |
130 | |
131 | void fireImageChanged { |
132 | set dirty; |
133 | imageChanged(getImage()); |
134 | } |
135 | |
136 | void _persist { |
137 | if (autoPersistFile != null && dirty) { |
138 | dirty = false; |
139 | savePNGVerbose(autoPersistFile, image); |
140 | } |
141 | } |
142 | |
143 | // doesn't allow drawing immediately |
144 | void loadImageProtected(File file) { |
145 | loadImage(file); |
146 | drawingAllowed(false); |
147 | } |
148 | |
149 | // Note: also auto-persists to that file |
150 | void loadImage(File file) { |
151 | close(); // persist |
152 | var img = loadImage2(file); |
153 | autoPersistFile(file); |
154 | setImage(img); |
155 | } |
156 | |
157 | void newImage() ctex { |
158 | close(); // persist |
159 | //if (dirty && !swingConfirm(imageSurface, "Discard image")) ret; |
160 | |
161 | var file = createAutoPersistFile(); |
162 | print("Created auto persist file: " + f2s(file)); |
163 | autoPersistFile(file); |
164 | setImage(makeNewImage()); |
165 | drawingAllowed(true); |
166 | dirty = false; |
167 | } |
168 | |
169 | // swap to make a new file every time user clicks "New image" |
170 | swappable File createAutoPersistFile() { ret autoPersistFile(); } |
171 | |
172 | JToggleButton colorSelectButton(Color color) { |
173 | var btn = jImageToggleButton(main newImage(16, color), |
174 | "Select color to draw with", -> setBrushColor(color), |
175 | eq(brushColor(), color)); |
176 | ret addToButtonGroup(colorButtonGroup, btn); |
177 | } |
178 | |
179 | L<Color> colors() { |
180 | ret colorPaletteByBits(1); // 8 primary colors |
181 | } |
182 | |
183 | L<WidthAndHeight> imageSizes() { |
184 | ret mapLL sixteenToNine_p( |
185 | 240, 360, 480, 720 |
186 | ); |
187 | } |
188 | |
189 | void setImageSize(WidthAndHeight size) { |
190 | if (!sameSize(getImage(), size)) |
191 | newImage(); |
192 | } |
193 | |
194 | close { |
195 | rstPersist.triggerAndWait(); |
196 | } |
197 | |
198 | transient swappable JComponent bottomLeftControls() { ret jpanel(); } |
199 | |
200 | void selectTool(ImageSurfaceMouseHandler tool) { |
201 | imageSurface.setTool(tool); |
202 | } |
203 | |
204 | JToggleButton toolSelectButton(ImageSurfaceMouseHandler tool, |
205 | S imageID, S toolTip) { |
206 | ret addToButtonGroup(toolSelectButtonGroup, jImageToggleButton(imageID, toolTip, |
207 | -> selectTool(tool), |
208 | imageSurface.hasTool(tool)); |
209 | } |
210 | |
211 | L<JComponent> makeToolSelectors() { |
212 | ret ll( |
213 | instaToolTip(-> "Draw on image with left mouse button" |
214 | + stringIf(!drawingAllowed, " (click lock icon to start)"), |
215 | toolSelectButton(scribbler, #1103080, null)), |
216 | toolSelectButton(dragger, #1103079, "Scroll image with left mouse button"), |
217 | toolSelectButton(selector, #1103081, "Select rectangular area with left mouse button") |
218 | ); |
219 | } |
220 | } |
download show line numbers debug dex old transpilations
Travelled to 4 computer(s): bhatertpkbcr, ekrmjmnbrukm, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1034387 |
Snippet name: | JPaintTool - it's totally like MS Paint |
Eternal ID of this version: | #1034387/93 |
Text MD5: | eac7f071cb23abd7156eef8cefb9b1f7 |
Transpilation MD5: | 3625af800b2dd13d063bee8619419ebc |
Author: | stefan |
Category: | javax / gui |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-03-20 18:34:40 |
Source code size: | 6769 bytes / 220 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 346 / 1008 |
Version history: | 92 change(s) |
Referenced in: | [show references] |