1 // 2 // PaintBrushTool.cs 3 // 4 // Author: 5 // Jonathan Pobst <monkey@jpobst.com> 6 // 7 // Copyright (c) 2010 Jonathan Pobst 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining a copy 10 // of this software and associated documentation files (the "Software"), to deal 11 // in the Software without restriction, including without limitation the rights 12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 // copies of the Software, and to permit persons to whom the Software is 14 // furnished to do so, subject to the following conditions: 15 // 16 // The above copyright notice and this permission notice shall be included in 17 // all copies or substantial portions of the Software. 18 // 19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 // THE SOFTWARE. 26 27 using System; 28 using Cairo; 29 using Gtk; 30 using Pinta.Core; 31 using Mono.Unix; 32 33 using Pinta.Tools.Brushes; 34 35 namespace Pinta.Tools 36 { 37 public class PaintBrushTool : BaseBrushTool 38 { 39 #region Properties 40 public override string Name { get { return Catalog.GetString ("Paintbrush"); } } 41 public override string Icon { get { return "Tools.Paintbrush.png"; } } 42 public override string StatusBarText { get { return Catalog.GetString ("Left click to draw with primary color, right click to draw with secondary color."); } } 43 44 public override Gdk.Cursor DefaultCursor { 45 get { 46 int iconOffsetX, iconOffsetY; 47 var icon = CreateIconWithShape ("Cursor.Paintbrush.png", 48 CursorShape.Ellipse, BrushWidth, 8, 24, 49 out iconOffsetX, out iconOffsetY); 50 return new Gdk.Cursor (Gdk.Display.Default, icon, iconOffsetX, iconOffsetY); 51 } 52 } 53 public override bool CursorChangesOnZoom { get { return true; } } 54 55 public override Gdk.Key ShortcutKey { get { return Gdk.Key.B; } } 56 public override int Priority { get { return 25; } } 57 #endregion 58 59 private BasePaintBrush default_brush; 60 private BasePaintBrush active_brush; 61 private ToolBarLabel brush_label; 62 private ToolBarComboBox brush_combo_box; 63 private Color stroke_color; 64 private Point last_point; 65 OnActivated()66 protected override void OnActivated () 67 { 68 base.OnActivated (); 69 70 PintaCore.PaintBrushes.BrushAdded += HandleBrushAddedOrRemoved; 71 PintaCore.PaintBrushes.BrushRemoved += HandleBrushAddedOrRemoved; 72 } 73 OnDeactivated(BaseTool newTool)74 protected override void OnDeactivated (BaseTool newTool) 75 { 76 base.OnDeactivated (newTool); 77 78 PintaCore.PaintBrushes.BrushAdded -= HandleBrushAddedOrRemoved; 79 PintaCore.PaintBrushes.BrushRemoved -= HandleBrushAddedOrRemoved; 80 } 81 OnBuildToolBar(Toolbar tb)82 protected override void OnBuildToolBar (Toolbar tb) 83 { 84 base.OnBuildToolBar (tb); 85 86 // Change the cursor when the BrushWidth is changed. 87 brush_width.ComboBox.Changed += (sender, e) => SetCursor (DefaultCursor); 88 89 tb.AppendItem (new Gtk.SeparatorToolItem ()); 90 91 if (brush_label == null) 92 brush_label = new ToolBarLabel (string.Format (" {0}: ", Catalog.GetString ("Type"))); 93 94 if (brush_combo_box == null) { 95 brush_combo_box = new ToolBarComboBox (100, 0, false); 96 brush_combo_box.ComboBox.Changed += (o, e) => { 97 Gtk.TreeIter iter; 98 if (brush_combo_box.ComboBox.GetActiveIter (out iter)) { 99 active_brush = (BasePaintBrush)brush_combo_box.Model.GetValue (iter, 1); 100 } else { 101 active_brush = default_brush; 102 } 103 }; 104 105 RebuildBrushComboBox (); 106 } 107 108 tb.AppendItem (brush_label); 109 tb.AppendItem (brush_combo_box); 110 } 111 112 /// <summary> 113 /// Rebuild the list of brushes when a brush is added or removed. 114 /// </summary> HandleBrushAddedOrRemoved(object sender, BrushEventArgs e)115 private void HandleBrushAddedOrRemoved (object sender, BrushEventArgs e) 116 { 117 RebuildBrushComboBox (); 118 } 119 120 /// <summary> 121 /// Rebuild the list of brushes. 122 /// </summary> RebuildBrushComboBox()123 private void RebuildBrushComboBox () 124 { 125 brush_combo_box.Model.Clear (); 126 default_brush = null; 127 128 foreach (var brush in PintaCore.PaintBrushes) { 129 if (default_brush == null) 130 default_brush = (BasePaintBrush)brush; 131 brush_combo_box.Model.AppendValues (brush.Name, brush); 132 } 133 134 brush_combo_box.ComboBox.Active = 0; 135 } 136 137 #region Mouse Handlers OnMouseDown(DrawingArea canvas, ButtonPressEventArgs args, PointD point)138 protected override void OnMouseDown (DrawingArea canvas, ButtonPressEventArgs args, PointD point) 139 { 140 base.OnMouseDown (canvas, args, point); 141 active_brush.DoMouseDown (); 142 } 143 OnMouseUp(DrawingArea canvas, ButtonReleaseEventArgs args, PointD point)144 protected override void OnMouseUp (DrawingArea canvas, ButtonReleaseEventArgs args, PointD point) 145 { 146 base.OnMouseUp (canvas, args, point); 147 active_brush.DoMouseUp (); 148 } 149 OnMouseMove(object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point)150 protected override void OnMouseMove (object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point) 151 { 152 Document doc = PintaCore.Workspace.ActiveDocument; 153 154 if (mouse_button == 1) { 155 stroke_color = PintaCore.Palette.PrimaryColor; 156 } else if (mouse_button == 3) { 157 stroke_color = PintaCore.Palette.SecondaryColor; 158 } else { 159 last_point = point_empty; 160 return; 161 } 162 163 // TODO: also multiply by pressure 164 stroke_color = new Color (stroke_color.R, stroke_color.G, stroke_color.B, 165 stroke_color.A * active_brush.StrokeAlphaMultiplier); 166 167 int x = (int)point.X; 168 int y = (int)point.Y; 169 170 if (last_point.Equals (point_empty)) 171 last_point = new Point (x, y); 172 173 if (doc.Workspace.PointInCanvas (point)) 174 surface_modified = true; 175 176 var surf = doc.CurrentUserLayer.Surface; 177 var invalidate_rect = Gdk.Rectangle.Zero; 178 var brush_width = BrushWidth; 179 180 using (var g = new Context (surf)) { 181 g.AppendPath (doc.Selection.SelectionPath); 182 g.FillRule = FillRule.EvenOdd; 183 g.Clip (); 184 185 g.Antialias = UseAntialiasing ? Antialias.Subpixel : Antialias.None; 186 g.LineWidth = brush_width; 187 g.LineJoin = LineJoin.Round; 188 g.LineCap = BrushWidth == 1 ? LineCap.Butt : LineCap.Round; 189 g.SetSourceColor (stroke_color); 190 191 invalidate_rect = active_brush.DoMouseMove (g, stroke_color, surf, 192 x, y, last_point.X, last_point.Y); 193 } 194 195 // If we draw partially offscreen, Cairo gives us a bogus 196 // dirty rectangle, so redraw everything. 197 if (doc.Workspace.IsPartiallyOffscreen (invalidate_rect)) { 198 doc.Workspace.Invalidate (); 199 } else { 200 doc.Workspace.Invalidate (doc.ClampToImageSize (invalidate_rect)); 201 } 202 203 last_point = new Point (x, y); 204 } 205 #endregion 206 } 207 } 208