kapstok / NHL-DePa2 (public) (License: Unspecified) (since 2018-11-09) (hash sha1)
Design Patterns school project; a GUI using multiple Design Patterns.
List of commits:
Subject Hash Author Date (UTC)
Add resize option. 961e464090918d83690ff66838f2fc96c6ad213a Jan Allersma 2018-09-13 18:29:44
Make `MoveCmd` Command-oriented. 2970a8d862285752551be2464422375ca958357f Jan Allersma 2018-09-12 15:33:17
Change of strategy: Make project command-oriented. ce40dffb1675661922411eeb07f148f61a176c22 Jan Allersma 2018-09-12 11:35:45
Initial commit. 38c8818349679937ae25dde38949202ec45ea7b2 Jan Allersma 2018-09-11 20:21:57
Commit 961e464090918d83690ff66838f2fc96c6ad213a - Add resize option.
Remove some obsolete funtions/variables.
Author: Jan Allersma
Author date (UTC): 2018-09-13 18:29
Committer name: Jan Allersma
Committer date (UTC): 2018-09-13 18:33
Parent(s): 2970a8d862285752551be2464422375ca958357f
Signing key:
Tree: b4a729a6e421821288a6ed27b633b47e3e9c9bf0
File Lines added Lines deleted
README.md 6 2
source/app.d 6 33
source/commands/shapeCommands.d 25 3
source/drawing/shapeOptions.d 15 1
source/entities/canvas.d 0 20
source/entities/shape.d 76 83
source/windows/mainWindow.d 5 15
source/windows/resizeDialog.d 63 0
File README.md changed (mode: 100644) (index 17074fb..9917291)
... ... use 'dub' to compile and run the code.
7 7
8 8 * CheckBounds werkt retrograde (laatst toegevoegde item eerst checken) en stopt na een collision. * CheckBounds werkt retrograde (laatst toegevoegde item eerst checken) en stopt na een collision.
9 9
10 * [DLang issue](https://forum.dlang.org/post/emixkjutxnnrplaziwkj@forum.dlang.org)
10 # TODO
11 11
12 * Moet nog checken op ongebruikte functies.
12 * Undo en redo werkt nog niet helemaal lekker. Waarschijnlijk als gevolg van het niet gebruiken van pointers in de 'shapes' array.
13
14 * File IO op undo redo afstemmen.
15
16 * Commands door een enkele klasse laten uitvoeren (?)
File source/app.d changed (mode: 100644) (index 7140f21..a68c161)
1 1 import gio.Application : GioApplication = Application; import gio.Application : GioApplication = Application;
2 2 import gtk.Application; import gtk.Application;
3 import gtk.ApplicationWindow;
4 import gtk.Box;
5 3
6 import dp.canvas;
7 import dp.menubar;
4 import dp.win.main;
8 5
9 import Global = dp.globals;
10
11 class win : ApplicationWindow
12 {
13 Canvas canvas;
14
15 this(Application application)
16 {
17 super(application);
18 setTitle("DPainter");
19 setBorderWidth(10);
20
21 Box container = new Box(Orientation.VERTICAL, 15);
22
23 Box menubar = new Menubar();
24 container.add(menubar);
25
26 canvas = new Canvas(500, 500);
27 container.add(canvas.area);
28 Global.canvas = &canvas;
29
30 add(container);
31 showAll();
32 }
33 }
34
35 int main(string[] args)
36 {
6 int main(string[] args) {
37 7 auto application = new Application ( "org.gtkd.demo.helloworld", GApplicationFlags.FLAGS_NONE ); auto application = new Application ( "org.gtkd.demo.helloworld", GApplicationFlags.FLAGS_NONE );
8
38 9 application.addOnActivate(delegate void(GioApplication app) { application.addOnActivate(delegate void(GioApplication app) {
39 new win(application);
10 Win win = new Win(application);
11 //new ResizeWin(application);
40 12 }); });
13
41 14 return application.run(args); return application.run(args);
42 15 } }
File source/commands/shapeCommands.d changed (mode: 100644) (index 06b10d2..1ca5444)
... ... public class MoveCmd : Command {
32 32 Shape original; Shape original;
33 33 Shape newShape; Shape newShape;
34 34
35 this (Shape original) {
35 this(Shape original) {
36 36 this.original = original; this.original = original;
37 newShape = new Shape();
38 Global.Brush.Shape = original.type;
37 newShape = original.clone();
39 38 Global.canvas.addShape(newShape); Global.canvas.addShape(newShape);
40 39 execute(); execute();
41 40 } }
 
... ... public class MoveCmd : Command {
50 49 newShape.visible = newShape.enabled = false; newShape.visible = newShape.enabled = false;
51 50 } }
52 51 } }
52
53 public class ResizeCmd : Command {
54 Shape original;
55 Shape newShape;
56
57 this(Shape original, float newSize) {
58 this.original = original;
59 newShape = original.clone();
60 Global.canvas.addShape(newShape);
61 newShape.resize(newSize);
62 execute();
63 }
64
65 public override void execute() {
66 original.visible = original.enabled = false;
67 newShape.visible = newShape.enabled = true;
68 }
69
70 public override void undo() {
71 original.visible = original.enabled = true;
72 newShape.visible = newShape.enabled = false;
73 }
74 }
File source/drawing/shapeOptions.d changed (mode: 100644) (index 657ec24..e59df47)
... ... import gtk.Widget;
5 5 import gdk.Event; import gdk.Event;
6 6
7 7 import dp.shape; import dp.shape;
8 import dp.win.resize;
8 9 import Global = dp.globals; import Global = dp.globals;
9 10
10 11 import std.stdio; // For debugging. import std.stdio; // For debugging.
11 12
12 13 protected Shape s; protected Shape s;
14 protected ResizeDialog rd;
13 15
14 16 public class ShapeOptions : Menu { public class ShapeOptions : Menu {
15 17 this() { this() {
16 18 super(); super();
17 19 this.append(new DeleteShape()); this.append(new DeleteShape());
18 20 this.append(new MoveShape()); this.append(new MoveShape());
21 this.append(new ResizeShape());
19 22 } }
20 23
21 24 // Will look like: sOptions.OfShape(s); Should be differently named. // Will look like: sOptions.OfShape(s); Should be differently named.
 
... ... protected class MoveShape : MenuItem {
50 53 writeln("Moved shape"); writeln("Moved shape");
51 54 s.select(); s.select();
52 55 s.enabled = false; s.enabled = false;
53 //s.queueRevival = true;
54 56 Global.canvas.repaint(); Global.canvas.repaint();
55 57 // Create ghost shape. // Create ghost shape.
56 58 return false; // Hide ShapeOptions when button is released. return false; // Hide ShapeOptions when button is released.
57 59 } }
58 60 } }
61
62 protected class ResizeShape : MenuItem {
63 this() {
64 super("Resize shape");
65 addOnButtonRelease(&relCallback);
66 }
67
68 private bool relCallback (Event event, Widget widget) {
69 rd = new ResizeDialog(s);
70 return false; // Hide ShapeOptions when button is released.
71 }
72 }
File source/entities/canvas.d changed (mode: 100644) (index ac3c76c..f4b246d)
... ... public class Canvas : Entity {
34 34 area = new DrawingArea(width, height); area = new DrawingArea(width, height);
35 35 shapeOptions = new ShapeOptions(); shapeOptions = new ShapeOptions();
36 36 area.addOnButtonPress(&clickCallback); area.addOnButtonPress(&clickCallback);
37 area.addOnScroll(&scrollCallback);
38 37 surface = ImageSurface.create(CairoFormat.ARGB32, width, height); surface = ImageSurface.create(CairoFormat.ARGB32, width, height);
39 38 area.addOnDraw(&drawCallback); area.addOnDraw(&drawCallback);
40 39 shapes.length = 0; shapes.length = 0;
 
... ... public class Canvas : Entity {
98 97 return true; return true;
99 98 } }
100 99
101 private bool scrollCallback (Event event, Widget widget) {
102 // Get mouse position relative to widget..
103 area.getPointer(mouseX, mouseY);
104
105 GdkScrollDirection dir;
106 event.getScrollDirection(dir);
107
108 /+
109 Look FIFO wether you clicked on a Shape.
110 FIFO is required to make sure the Shape
111 most at front is selected first.
112 +/
113 for(int i = cast(int)shapes.length - 1; i >= 0; i -= 1)
114 if(shapes[i].CheckBounds(mouseX, mouseY, dir))
115 break;
116
117 return true;
118 }
119
120 100 private void clearCanvas() { private void clearCanvas() {
121 101 Context c = Context.create(surface); Context c = Context.create(surface);
122 102 c.setSourceRgba(1,1,1,1); c.setSourceRgba(1,1,1,1);
File source/entities/shape.d changed (mode: 100644) (index bb2a901..cde8389)
... ... import cairo.Context;
6 6 import cairo.ImageSurface; import cairo.ImageSurface;
7 7
8 8 import std.stdio; // For debug import std.stdio; // For debug
9 import std.conv; // Redundant?
9 import std.conv;
10 10
11 11 import dp.history; import dp.history;
12 12 import dp.command.shape; import dp.command.shape;
13 13
14 14 class Shape : Global.Entity { class Shape : Global.Entity {
15 private Context c;
16 public bool visible; // Should be encapsulated.
17 private string shapeType;
18
19 15 /+ /+
20 16 bounds[0][0] = minX bounds[0][0] = minX
21 17 bounds[1][0] = maxX bounds[1][0] = maxX
22 18 bounds[0][1] = minY bounds[0][1] = minY
23 19 bounds[1][1] = maxY bounds[1][1] = maxY
24 20 +/ +/
21
22 private Context c;
23 public bool visible; // Should be encapsulated.
24 private string shapeType;
25 25 private double[2][2] bounds; private double[2][2] bounds;
26 private double size = 0;
26 27
27 28 this() { this() {
28 29 enabled = visible = true; enabled = visible = true;
29 30 } }
30 31
31 @property
32 public double[2] position() {
33 return bounds[0];
34 }
35
36 @property
37 public double[2] position(double[2] newPos) {
38 double sX = sizeX;
39 double sY = sizeY;
40
41 bounds[1][0] = newPos[0] + sX;
42 bounds[1][1] = newPos[1] + sY;
32 this(double[2][2] bounds, double size, string shapeType) {
33 enabled = visible = true;
34 this.bounds = bounds;
35 this.size = size;
36 this.shapeType = shapeType;
43 37
44 return bounds[0] = newPos;
38 writeln(this.bounds);writeln(this.size);writeln(this.type);
45 39 } }
46 40
47 @property
48 public double sizeX () {
49 return bounds[1][0] - bounds[0][0];
50 }
41 public void resize(float factor) {
42 bounds[1][1] += factor / 2;
43 bounds[1][0] += factor / 2;
44 bounds[0][1] -= factor / 2;
45 bounds[0][0] -= factor / 2;
51 46
52 @property
53 public double sizeY () {
54 return bounds[1][1] - bounds[0][1];
47 size += factor;
48 draw();
55 49 } }
56 50
57 51 @property @property
58 public override string type () {
52 public override string type() {
59 53 return shapeType; return shapeType;
60 54 } }
61 55
62 56 public override string toString() { public override string toString() {
63 return text(type, " ", bounds[0][0], " ", bounds[0][1], " ", sizeX, " ", sizeY);
57 return text(type, " ", bounds[0][0], " ", bounds[0][1], " ", size, " ", size);
58 }
59
60 public Shape clone() {
61 return new Shape(bounds, size, type);
64 62 } }
65 63
66 64 public void move(int x, int y, Context context=null) { public void move(int x, int y, Context context=null) {
 
... ... class Shape : Global.Entity {
70 68 draw(x,y); draw(x,y);
71 69 } }
72 70
73 public void select () {
71 public void select() {
74 72 c.setSourceRgba(1-Global.Brush.Red, 1-Global.Brush.Green, 1-Global.Brush.Blue, 1); c.setSourceRgba(1-Global.Brush.Red, 1-Global.Brush.Green, 1-Global.Brush.Blue, 1);
75 73 c.stroke(); c.stroke();
76 74 c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha); c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha);
77 75 } }
78 76
79 public void remove () {
77 public void remove() {
80 78 visible = enabled = false; visible = enabled = false;
81 79 } }
82 80
81 private void initSize() {
82 shapeType = Global.Brush.Shape;
83 Global.Brush.Shape = null;
84
85 final switch (type) {
86 case "rectangle":
87 size = 125;
88 break;
89 case "ellipse":
90 case "circle":
91 size = 50;
92 break;
93 }
94 }
95
83 96 private void draw(int x, int y) { private void draw(int x, int y) {
97 if(size == 0) initSize();
98
99 final switch (type) {
100 case "rectangle":
101 bounds[0][0] = x - size / 2; // minX
102 bounds[1][0] = x + size / 2; // maxX
103 bounds[0][1] = y - size / 2; // minY
104 bounds[1][1] = y + size / 2; // maxY
105 break;
106 case "ellipse":
107 bounds[0][0] = x - size / 2; // minX
108 bounds[1][0] = x + size / 2; // maxX
109 bounds[0][1] = y - size; // minY
110 bounds[1][1] = y + size; // maxY
111 break;
112 case "circle":
113 bounds[0][0] = x - size; // minX
114 bounds[1][0] = x + size; // maxX
115 bounds[0][1] = y - size; // minY
116 bounds[1][1] = y + size; // maxY
117 break;
118 }
119 draw();
120 }
121
122 private void draw() {
84 123 if(visible) { if(visible) {
85 shapeType = Global.Brush.Shape;
86 final switch (Global.Brush.Shape) {
124 final switch (type) {
87 125 case "rectangle": case "rectangle":
88 126 c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha); c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha);
89 c.rectangle(x - 125 / 2, y - 125 / 2, 125, 125);
90 c.fillPreserve();
91
92 bounds[0][0] = x - 125 / 2; // minX
93 bounds[1][0] = x + 125 / 2; // maxX
94 bounds[0][1] = y - 125 / 2; // minY
95 bounds[1][1] = y + 125 / 2; // maxY
96
127 c.rectangle(bounds[0][0], bounds[0][1], size, size);
128 applyToSource();
97 129 break; break;
98 130 case "ellipse": case "ellipse":
99 131 c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha); c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha);
100 132 c.scale(0.5, 1); c.scale(0.5, 1);
101 c.arc(x*2,y,50,0,2*3.141); // 3.141 = Pi
102 c.fillPreserve();
103
104 bounds[0][0] = x - 50 / 2; // minX
105 bounds[1][0] = x + 50 / 2; // maxX
106 bounds[0][1] = y - 50; // minY
107 bounds[1][1] = y + 50; // maxY
108
133 c.arc((bounds[0][0]+size/2)*2 ,bounds[0][1]+size ,size,0,2*3.141); // 3.141 = Pi
134 applyToSource();
109 135 break; break;
110 136 case "circle": case "circle":
111 137 c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha); c.setSourceRgba(Global.Brush.Red, Global.Brush.Green, Global.Brush.Blue, Global.Brush.Alpha);
112 c.arc(x,y,50,0,2*3.141); // 3.141 = Pi
113 c.fillPreserve();
114
115 bounds[0][0] = x - 50; // minX
116 bounds[1][0] = x + 50; // maxX
117 bounds[0][1] = y - 50; // minY
118 bounds[1][1] = y + 50; // maxY
119
138 c.arc(bounds[0][0]+size, bounds[0][1]+size, size,0,2*3.141); // 3.141 = Pi
139 applyToSource();
120 140 break; break;
121 141 } }
122 142 } }
123 Global.Brush.Shape = null;
124 143 } }
125 144
126 public bool CheckBounds (int x, int y) {
145 public bool CheckBounds(int x, int y) {
127 146 if(enabled) { if(enabled) {
128 147 writeln(shapeType ~ ":"); writeln(shapeType ~ ":");
129 148 write(bounds[0][0]);write(" <- ");write(x);write(" -> ");writeln(bounds[1][0]); write(bounds[0][0]);write(" <- ");write(x);write(" -> ");writeln(bounds[1][0]);
 
... ... class Shape : Global.Entity {
145 164 return false; return false;
146 165 } }
147 166
148 // This will be used when the user scrolls on the canvas.
149 public bool CheckBounds (int x, int y, int growContext) {
150 if(enabled) {
151 if(
152 x > bounds[0][0] && // minX
153 x < bounds[1][0] && // maxX
154 y > bounds[0][1] && // minY
155 y < bounds[1][1] // maxY
156 ) {
157 if(growContext == 0) {
158 bounds[0][0] -= 5;
159 bounds[1][0] += 5;
160 bounds[0][1] -= 5;
161 bounds[1][1] += 5;
162 } else {
163 bounds[0][0] += 5;
164 bounds[1][0] -= 5;
165 bounds[0][1] += 5;
166 bounds[1][1] -= 5;
167 }
168 return true;
169 }
170 }
171 return false;
172 }
173
174 167 public void applyToSource() { public void applyToSource() {
175 168 if(visible) if(visible)
176 169 c.fillPreserve(); c.fillPreserve();
File source/windows/mainWindow.d copied from file source/app.d (similarity 55%) (mode: 100644) (index 7140f21..10e234b)
1 import gio.Application : GioApplication = Application;
1 module dp.win.main;
2
2 3 import gtk.Application; import gtk.Application;
3 4 import gtk.ApplicationWindow; import gtk.ApplicationWindow;
4 5 import gtk.Box; import gtk.Box;
 
... ... import dp.menubar;
8 9
9 10 import Global = dp.globals; import Global = dp.globals;
10 11
11 class win : ApplicationWindow
12 {
12 class Win : ApplicationWindow {
13 13 Canvas canvas; Canvas canvas;
14 14
15 this(Application application)
16 {
15 this(Application application) {
17 16 super(application); super(application);
18 17 setTitle("DPainter"); setTitle("DPainter");
19 18 setBorderWidth(10); setBorderWidth(10);
20 19
21 20 Box container = new Box(Orientation.VERTICAL, 15); Box container = new Box(Orientation.VERTICAL, 15);
22
21
23 22 Box menubar = new Menubar(); Box menubar = new Menubar();
24 23 container.add(menubar); container.add(menubar);
25 24
 
... ... class win : ApplicationWindow
31 30 showAll(); showAll();
32 31 } }
33 32 } }
34
35 int main(string[] args)
36 {
37 auto application = new Application ( "org.gtkd.demo.helloworld", GApplicationFlags.FLAGS_NONE );
38 application.addOnActivate(delegate void(GioApplication app) {
39 new win(application);
40 });
41 return application.run(args);
42 }
File source/windows/resizeDialog.d added (mode: 100644) (index 0000000..dc961a4)
1 module dp.win.resize;
2
3 import gtk.Dialog;
4 import gtk.MessageDialog;
5 import gtk.Label;
6 import gtk.Button;
7 import gtk.Entry;
8
9 import dp.shape;
10 import dp.history;
11 import dp.command.shape;
12
13 public import Global = dp.globals;
14
15 import std.stdio; // Debug
16
17 import std.string, std.conv;
18
19 class ResizeDialog : Dialog {
20 Shape shape;
21 Entry factorEntry;
22
23 this(Shape shape) {
24 super();
25 setTitle("Resize shape");
26 this.shape = shape;
27
28 factorEntry = new Entry();
29 factorEntry.setPlaceholderText("factor");
30
31 Button confirmBtn = new Button("Resize!", &clickCallback);
32
33 // Content area, containing non-interactable GTK objects.
34 getContentArea().add(new Label("By what factor should the shape be resized?"));
35 getContentArea().setBorderWidth(20);
36
37 // Action area, containing interactable GTK objects.
38 getActionArea().add(factorEntry);
39 getActionArea().add(confirmBtn);
40
41 showAll();
42 }
43
44 void clickCallback (Button button) {
45 float factor = 1.3;
46 string input = factorEntry.getText();
47
48 if(isNumeric(input)) {
49 factor = parse!float(input);
50 History.addCommand(new ResizeCmd(shape, factor));
51 this.close();
52 }
53 else {
54 MessageDialog msgd = new MessageDialog (
55 this, DialogFlags.MODAL, MessageType.ERROR, ButtonsType.OK,
56 "'" ~ input ~ "' is an invalid factor. Please use numbers only.",
57 null
58 );
59 scope(exit) msgd.destroy();
60 msgd.run();
61 }
62 }
63 }
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/kapstok/NHL-DePa2

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/kapstok/NHL-DePa2

Clone this repository using git:
git clone git://git.rocketgit.com/user/kapstok/NHL-DePa2

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main