1 /* DrawOfPages: Take notes with touchscreen input. 2 * Copyright (C) 2017 Marko Semet(Marko10_000) 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 module drawofpages.gui.drawArea; 18 19 private 20 { 21 import cairo.Context; 22 import cairo.ImageSurface; 23 import cairo.Surface; 24 import drawofpages.draw; 25 import drawofpages.gui; 26 import gdk.Device; 27 import gtk.DrawingArea; 28 import gdk.Event; 29 import gtk.Widget; 30 import std.math; 31 } 32 33 private final class DrawPart 34 { 35 package ImageSurface surface; 36 private Context context; 37 38 public Point2D pos; 39 public double scale; 40 public Square box; 41 42 pragma(inline, true) 43 private pure nothrow void updatePoint(ref Point2D p) 44 { 45 p = (p - this.pos) * this.scale; 46 } 47 pragma(inline, true) 48 private pure nothrow void updateScale(ref double s) 49 { 50 s *= this.scale; 51 } 52 53 public this(Point2D pos, double scale) 54 { 55 this.surface = ImageSurface.create(cairo_format_t.ARGB32, 512, 512); 56 this.context = Context.create(this.surface); 57 this.pos = pos; 58 this.scale = scale; 59 60 // Set background color 61 this.context.setSourceRgb(1, 1, 1); 62 this.context.rectangle(0, 0, 512, 512); 63 this.context.fill(); 64 65 // Box 66 this.box = Square(pos, pos + Point2D([512 / this.scale, 512 / this.scale])); 67 } 68 public ~this() 69 { 70 this.surface.destroy; 71 } 72 73 public void drawLine(Point2D from, Point2D to, double size, Color color) 74 { 75 this.updatePoint(from); 76 this.updatePoint(to); 77 this.updateScale(size); 78 79 this.context.setSourceRgba(color.r, color.g, color.b, color.a); 80 this.context.moveTo(from.dims[0], from.dims[1]); 81 this.context.setLineWidth(size); 82 this.context.lineTo(to.dims[0], to.dims[1]); 83 this.context.stroke(); 84 this.context.moveTo(0, 0); 85 } 86 } 87 88 private final class PardDraw : Draw 89 { 90 private DrawPart dp; 91 92 public this(DrawPart dp) 93 { 94 this.dp = dp; 95 } 96 97 public void drawCirc(Point2D center, float radius, Color color) 98 { 99 assert(false); 100 } 101 public void drawLine(Point2D from, Point2D to, double size, Color color) 102 { 103 dp.drawLine(from, to, size, color); 104 } 105 public void drawRect(Square square, Color color) 106 { 107 assert(false); // TODO: draw rect 108 } 109 public void redraw() {} 110 } 111 112 private final class Grid 113 { 114 public DrawPart[][] parts; 115 116 public Point2D rel; 117 public Point2D relZero; 118 public double scale; 119 public long x; 120 public long y; 121 public ulong width; 122 public ulong height; 123 124 private pure void calcRelZero() 125 { 126 this.relZero = Point2D([this.x, this.y]) * this.scale; 127 } 128 129 public this() 130 { 131 this.scale = 1; 132 this.x = -256; 133 this.y = -256; 134 this.rel = Point2D([this.x, this.y]); 135 this.width = 0; 136 this.height = 0; 137 this.parts = new DrawPart[][0]; 138 this.calcRelZero(); 139 } 140 141 public void resize(ulong width, ulong height, GuiInteraction interatcion) 142 { 143 synchronized 144 { 145 ulong partsX = (width - this.x) / 512 + ((width - this.x) % 512 == 0 ? 0 : 1); 146 ulong partsY = (height - this.y) / 512 + ((height - this.y) % 512 == 0 ? 0 : 1); 147 DrawPart[][] newParts = new DrawPart[][partsY]; 148 for(ulong y = 0; y < partsY; y++) 149 { 150 if(this.parts.length > y) 151 { 152 // Use old data 153 DrawPart[] old = this.parts[y]; 154 DrawPart[] tmp = new DrawPart[partsX]; 155 for(ulong x = 0; x < partsX; x++) 156 { 157 if(old.length > x) 158 { 159 tmp[x] = old[x]; 160 } 161 else 162 { 163 tmp[x] = new DrawPart(Point2D([x, y]) * (512 / this.scale) + this.rel, this.scale); 164 interatcion.redraw(tmp[x].box, new PardDraw(tmp[x])); 165 } 166 } 167 newParts[y] = tmp; 168 } 169 else 170 { 171 // Gen new array 172 DrawPart[] tmp = new DrawPart[partsX]; 173 for(ulong x = 0; x < partsX; x++) 174 { 175 tmp[x] = new DrawPart(Point2D([x, y]) * (512 / this.scale) + this.rel, this.scale); 176 interatcion.redraw(tmp[x].box, new PardDraw(tmp[x])); 177 } 178 newParts[y] = tmp; 179 } 180 } 181 182 // Set new data 183 { 184 DrawPart[][] tmp = this.parts; 185 this.parts = newParts; 186 tmp.destroy; 187 this.width = width; 188 this.height = height; 189 } 190 } 191 } 192 } 193 194 private final class DrawHandler : Draw 195 { 196 private DrawElement de; 197 package Grid grid; 198 199 public this(DrawElement de) 200 { 201 this.de = de; 202 this.grid = new Grid(); 203 } 204 205 public void drawCirc(Point2D center, float radius, Color color) 206 { 207 assert(false); 208 } 209 public void drawLine(Point2D from, Point2D to, double size, Color color) 210 { 211 foreach(DrawPart[] dps; this.grid.parts) 212 { 213 foreach(DrawPart dp; dps) 214 { 215 dp.drawLine(from, to, size, color); 216 } 217 } 218 } 219 public void drawRect(Square square, Color color) 220 { 221 assert(false); 222 } 223 public void redraw() 224 { 225 this.de.queueDraw(); 226 } 227 228 package void _draw(Context cr) 229 { 230 for(long i = 0; i < this.grid.parts.length; i++) 231 { 232 DrawPart[] io = this.grid.parts[i]; 233 for(long j = 0; j < io.length; j++) 234 { 235 cr.setSourceSurface(io[j].surface, (j * 512) + (cast(long) this.grid.x), (i * 512) + (cast(long) this.grid.y)); 236 cr.rectangle((j * 512) + (cast(long) this.grid.x), (i * 512) + (cast(long) this.grid.y), 512, 512); 237 cr.fill(); 238 } 239 } 240 cr.save(); 241 cr.destroy; 242 } 243 } 244 245 public class DrawElement : DrawingArea 246 { 247 private DrawHandler dh; 248 private GuiInteraction interatcion = null; 249 private bool hasDown = false; 250 251 private static pure CURSOR_TYPE _getCursor(GdkInputSource gis) 252 { 253 if(gis == GdkInputSource.PEN) 254 { 255 return CURSOR_TYPE.PEN; 256 } 257 else if(gis == GdkInputSource.ERASER) 258 { 259 return CURSOR_TYPE.ERASER; 260 } 261 else if(gis == GdkInputSource.MOUSE) 262 { 263 return CURSOR_TYPE.MOUSE; 264 } 265 return CURSOR_TYPE.HAND; 266 } 267 268 private struct _pointerData 269 { 270 string deviceID = null; 271 double pressure; 272 CURSOR_TYPE ctype; 273 274 this(Device device) 275 { 276 if(device.getDeviceType() != GdkDeviceType.MASTER) 277 { 278 this.deviceID = device.getProductId(); 279 } 280 GdkAxisFlags axes = device.getAxes(); 281 this.pressure = axes.PRESSURE; 282 this.ctype = _getCursor(device.getSource()); 283 } 284 } 285 286 public this() 287 { 288 super(); 289 this.dh = new DrawHandler(this); 290 this.addOnDraw(delegate bool(Context cr, Widget w) { 291 synchronized 292 { 293 this._draw(cr); 294 } 295 return false; 296 }); 297 this.addOnButtonPress(delegate bool(Event e, Widget w) { 298 if(this.interatcion is null) 299 { 300 return false; 301 } 302 303 GdkEventButton* geb = e.button(); 304 _pointerData tmp = _pointerData(e.getDevice()); 305 synchronized 306 { 307 this.interatcion.down(Point2D([geb.x, geb.y]), tmp.ctype, tmp.pressure, tmp.deviceID); 308 } 309 this.hasDown = true; 310 return false; 311 }); 312 this.addOnMotionNotify(delegate bool(Event e, Widget w) { 313 if((this.interatcion is null) || (!this.hasDown)) 314 { 315 return false; 316 } 317 318 GdkEventMotion* motion = e.motion(); 319 _pointerData tmp = _pointerData(e.getDevice()); 320 synchronized 321 { 322 this.interatcion.contin(Point2D([motion.x, motion.y]), tmp.ctype, tmp.pressure, tmp.deviceID); 323 } 324 return false; 325 }); 326 this.addOnButtonRelease(delegate bool(Event e, Widget w) { 327 if(this.interatcion is null) 328 { 329 return false; 330 } 331 332 GdkEventButton* geb = e.button(); 333 _pointerData tmp = _pointerData(e.getDevice()); 334 this.hasDown = false; 335 synchronized 336 { 337 this.interatcion.up(Point2D([geb.x, geb.y]), tmp.ctype, tmp.pressure, tmp.deviceID); 338 } 339 return false; 340 }); 341 this.addOnSizeAllocate(delegate void(Allocation alloc, Widget w) { 342 synchronized(this) 343 { 344 this.dh.grid.resize(alloc.width, alloc.height, this.interatcion); 345 } 346 }); 347 } 348 349 private void _draw(Context cr) 350 { 351 this.dh._draw(cr); 352 } 353 public DrawHandler getDrawHanlder() 354 { 355 return this.dh; 356 } 357 public ref GuiInteraction getGuiInteraction() 358 { 359 return this.interatcion; 360 } 361 }