├── ShittyMouse.exe ├── badUIbattle.ttf ├── AprilCmd ├── cmd.exe └── fr-FR │ └── cmd.exe.mui ├── SMouse.cs ├── ScrollBall.cs ├── oRollerDialog.cs ├── o3dBallDialog.cs ├── CylinderList.cs ├── TreeView4.cs └── TreeView8.cs /ShittyMouse.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HerissonMignion/r-badUIbattle_1/HEAD/ShittyMouse.exe -------------------------------------------------------------------------------- /badUIbattle.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HerissonMignion/r-badUIbattle_1/HEAD/badUIbattle.ttf -------------------------------------------------------------------------------- /AprilCmd/cmd.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HerissonMignion/r-badUIbattle_1/HEAD/AprilCmd/cmd.exe -------------------------------------------------------------------------------- /AprilCmd/fr-FR/cmd.exe.mui: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HerissonMignion/r-badUIbattle_1/HEAD/AprilCmd/fr-FR/cmd.exe.mui -------------------------------------------------------------------------------- /SMouse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Drawing; 7 | using System.Windows.Forms; 8 | 9 | namespace ShittyMouse 10 | { 11 | public class SMouse 12 | { 13 | private Form forme; 14 | private PictureBox ImageBox; 15 | 16 | 17 | 18 | private int wSize = 50; 19 | private Color TColor = Color.FromArgb(3, 3, 3); 20 | 21 | 22 | 23 | public void Show() 24 | { 25 | this.forme.Show(); 26 | this.Timer.Start(); 27 | 28 | Cursor.Hide(); 29 | } 30 | public void Close() 31 | { 32 | this.Timer.Stop(); 33 | this.forme.Close(); 34 | 35 | Cursor.Show(); 36 | 37 | this.Timer.Dispose(); 38 | this.ImageBox.Dispose(); 39 | this.forme.Dispose(); 40 | } 41 | 42 | 43 | 44 | public SMouse() 45 | { 46 | this.forme = new Form(); 47 | this.forme.FormBorderStyle = FormBorderStyle.None; 48 | this.forme.StartPosition = FormStartPosition.Manual; 49 | this.forme.TopMost = true; 50 | this.forme.ShowInTaskbar = false; 51 | this.forme.MinimumSize = new Size(1, 1); 52 | this.forme.Size = new Size(this.wSize, this.wSize); 53 | this.forme.TransparencyKey = this.TColor; 54 | 55 | 56 | this.ImageBox = new PictureBox(); 57 | this.ImageBox.Parent = this.forme; 58 | this.ImageBox.Dock = DockStyle.Fill; 59 | 60 | 61 | 62 | 63 | 64 | this.Timer = new Timer(); 65 | this.Timer.Interval = 25; 66 | this.Timer.Tick += new EventHandler(this.Timer_Tick); 67 | 68 | 69 | 70 | } 71 | 72 | 73 | 74 | 75 | private Point FarLastMousePos = new Point(0, 0); //dernière position de la souris, mais à une distance résonable 76 | private double CurrentAngle = 0d; 77 | 78 | 79 | private Timer Timer; 80 | private void Timer_Tick(object sender, EventArgs e) 81 | { 82 | Point mpos = Cursor.Position; 83 | 84 | //vérifie si l'angle de la souris est à mettre à jour 85 | if (20 <= ((mpos.X - this.FarLastMousePos.X) * (mpos.X - this.FarLastMousePos.X)) + ((mpos.Y - this.FarLastMousePos.Y) * (mpos.Y - this.FarLastMousePos.Y))) 86 | { 87 | //on recalcul l'angle de la souris 88 | this.CurrentAngle = Math.Atan2(mpos.Y - this.FarLastMousePos.Y, mpos.X - this.FarLastMousePos.X); 89 | 90 | this.FarLastMousePos = mpos; 91 | 92 | 93 | 94 | //this.CurrentAngle = -0.7853d; 95 | //Program.wdebug(this.CurrentAngle); 96 | 97 | } 98 | 99 | 100 | this.RefreshForm(); 101 | } 102 | 103 | 104 | 105 | //repositionne la forme et refresh l'image de la souris 106 | private void RefreshForm() 107 | { 108 | Point mpos = Cursor.Position; 109 | 110 | 111 | 112 | //l'angle actuel 113 | double angle = this.CurrentAngle; 114 | 115 | double dwSize = (double)(this.wSize); 116 | 117 | //décalage supplémentaire sur la position de la forme 118 | int supdX = 0; 119 | int supdY = 0; 120 | 121 | 122 | double pi4 = Math.PI / 4d; 123 | Point ppos = new Point(0, 0); //position de l'extrémité de la souris dans l'image finale 124 | if (angle > -pi4 && angle < pi4) 125 | { 126 | ppos.X = this.wSize; 127 | ppos.Y = (int)((dwSize / 2d) + (Math.Sin(angle) * dwSize / 2d)); 128 | 129 | supdX = -1; 130 | } 131 | else if (angle <= -pi4 && angle > -pi4 * 3d) 132 | { 133 | ppos.Y = 0; 134 | ppos.X = (int)((dwSize / 2d) + (Math.Cos(angle) * dwSize / 2d)); 135 | 136 | supdY = 2; 137 | } 138 | else if (angle >= pi4 && angle < pi4 * 3d) 139 | { 140 | ppos.Y = this.wSize; 141 | ppos.X = (int)((dwSize / 2d) + (Math.Cos(angle) * dwSize / 2d)); 142 | 143 | supdY = -2; 144 | } 145 | else 146 | { 147 | ppos.X = 0; 148 | ppos.Y = (int)((dwSize / 2d) + (Math.Sin(angle) * dwSize / 2d)); 149 | 150 | supdX = 3; 151 | } 152 | 153 | 154 | 155 | 156 | Point newformpos = mpos; 157 | newformpos.X -= ppos.X; 158 | newformpos.Y -= ppos.Y; 159 | newformpos.X += supdX; 160 | newformpos.Y += supdY; 161 | this.forme.Location = newformpos; 162 | //this.forme.Top = mpos.Y + 1; 163 | //this.forme.Left = mpos.X + 1; 164 | 165 | 166 | //crée l'image du curseur 167 | Bitmap cimg = new Bitmap(20, 20); 168 | Graphics cg = Graphics.FromImage(cimg); 169 | cg.Clear(this.TColor); 170 | this.forme.Cursor.Draw(cg, new Rectangle(1, 0, 1, 1)); 171 | cg.Dispose(); 172 | 173 | 174 | //on crée l'image 175 | Bitmap img = new Bitmap(this.wSize, this.wSize); 176 | Graphics g = Graphics.FromImage(img); 177 | g.Clear(this.TColor); 178 | 179 | //g.FillRectangle(Brushes.Red, new Rectangle(ppos.X - 1, ppos.Y - 1, 3, 3)); 180 | 181 | 182 | g.TranslateTransform((float)(ppos.X), (float)(ppos.Y)); 183 | g.RotateTransform((float)(angle / Math.PI * 180d) + 90); 184 | g.TranslateTransform((float)(-ppos.X), (float)(-ppos.Y)); 185 | //Program.wdebug(angle); 186 | g.DrawImage(cimg, ppos.X, ppos.Y); 187 | //this.forme.Cursor.Draw(g, new Rectangle(ppos.X, ppos.Y, 1, 1)); 188 | 189 | 190 | g.Dispose(); 191 | cimg.Dispose(); 192 | if (this.ImageBox.Image != null) { this.ImageBox.Image.Dispose(); } 193 | this.ImageBox.Image = img; 194 | } 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /ScrollBall.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Drawing; 7 | using System.Windows.Forms; 8 | 9 | namespace C_FormTest1 10 | { 11 | 12 | 13 | public class ScrollBallEventArgs : EventArgs 14 | { 15 | public int DeltaX; 16 | public int DeltaY; 17 | public ScrollBallEventArgs(int StartDeltaX, int StartDeltaY) 18 | { 19 | this.DeltaX = StartDeltaX; 20 | this.DeltaY = StartDeltaY; 21 | } 22 | } 23 | 24 | public class ScrollBall 25 | { 26 | private Point MousePos { get { return this.ImageBox.PointToClient(Cursor.Position); } } 27 | 28 | private Form forme; 29 | private PictureBox ImageBox; 30 | 31 | 32 | 33 | 34 | public event EventHandler Scroll; 35 | private void Raise_Scroll(int sdx, int sdy) 36 | { 37 | if (this.Scroll != null) 38 | { 39 | this.Scroll(this, new ScrollBallEventArgs(sdx, sdy)); 40 | } 41 | } 42 | 43 | 44 | public void SetPos(Point p) { this.SetPos(p.X, p.Y); } 45 | public void SetPos(int mLeft, int mTop) 46 | { 47 | this.forme.Top = mTop - (this.forme.Height / 2); 48 | this.forme.Left = mLeft - (this.forme.Width / 2); 49 | 50 | } 51 | 52 | 53 | 54 | public void Show() 55 | { 56 | 57 | 58 | this.forme.Show(); 59 | this.RefreshImage(); 60 | 61 | } 62 | public void Close() 63 | { 64 | 65 | this.AnalyTimer.Stop(); //juste pour être sûr 66 | this.forme.Close(); 67 | 68 | } 69 | 70 | public ScrollBall() 71 | { 72 | this.forme = new Form(); 73 | this.forme.Text = "Scroll Ball"; 74 | this.forme.FormBorderStyle = FormBorderStyle.FixedToolWindow; 75 | this.forme.Opacity = 0.85d; 76 | this.forme.StartPosition = FormStartPosition.Manual; 77 | this.forme.ShowInTaskbar = false; 78 | this.forme.TopMost = true; 79 | this.forme.Size = new Size(120, 140); 80 | this.forme.MaximizeBox = false; 81 | this.forme.MinimizeBox = false; 82 | this.forme.TransparencyKey = Color.Blue; 83 | 84 | 85 | this.ImageBox = new PictureBox(); 86 | this.ImageBox.Parent = this.forme; 87 | this.ImageBox.Dock = DockStyle.Fill; 88 | this.ImageBox.MouseDown += new MouseEventHandler(this.ImageBox_MouseDown); 89 | this.ImageBox.MouseUp += new MouseEventHandler(this.ImageBox_MouseUp); 90 | 91 | 92 | 93 | this.CreateMap(); 94 | this.CreateTimer(); 95 | 96 | } 97 | private void ImageBox_MouseDown(object sender, MouseEventArgs e) 98 | { 99 | if (e.Button == MouseButtons.Left) 100 | { 101 | this.MouseDown(); 102 | } 103 | } 104 | private void ImageBox_MouseUp(object sender, MouseEventArgs e) 105 | { 106 | if (e.Button == MouseButtons.Left) 107 | { 108 | this.MouseUp(); 109 | } 110 | } 111 | 112 | 113 | 114 | 115 | #region map 116 | private DotMap map; 117 | 118 | private class Dot 119 | { 120 | public double x = 1d; 121 | public double y = 1d; 122 | public double z = 1d; 123 | 124 | public void AdjustRadius(double r = 1d) 125 | { 126 | double div = Math.Sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z)) / r; 127 | this.x /= div; 128 | this.y /= div; 129 | this.z /= div; 130 | } 131 | 132 | public void RotateOnY(double rad) 133 | { 134 | this.RotateOnY(Math.Sin(rad), Math.Cos(rad)); 135 | } 136 | public void RotateOnY(double sinn, double coss) 137 | { 138 | double newx = (this.x * coss) - (this.z * sinn); 139 | double newz = (this.x * sinn) + (this.z * coss); 140 | this.x = newx; 141 | this.z = newz; 142 | } 143 | public void RotateOnX(double rad) 144 | { 145 | this.RotateOnX(Math.Sin(rad), Math.Cos(rad)); 146 | } 147 | public void RotateOnX(double sinn, double coss) 148 | { 149 | double newz = (this.z * coss) - (this.y * sinn); 150 | double newy = (this.z * sinn) + (this.y * coss); 151 | this.z = newz; 152 | this.y = newy; 153 | } 154 | 155 | public Dot() { } 156 | public Dot(double sx, double sy, double sz) 157 | { 158 | this.x = sx; 159 | this.y = sy; 160 | this.z = sz; 161 | } 162 | } 163 | private class DotMap 164 | { 165 | public List listDot = new List(); 166 | 167 | //les nombre complex sont utilisé pour effecter les rotation 168 | public void RotateOnY(double rad) 169 | { 170 | double sinn = Math.Sin(rad); 171 | double coss = Math.Cos(rad); 172 | 173 | foreach (Dot d in this.listDot) 174 | { 175 | d.RotateOnY(sinn, coss); 176 | } 177 | } 178 | public void RotateOnX(double rad) 179 | { 180 | double sinn = Math.Sin(rad); 181 | double coss = Math.Cos(rad); 182 | 183 | foreach (Dot d in this.listDot) 184 | { 185 | d.RotateOnX(sinn, coss); 186 | } 187 | } 188 | 189 | 190 | public void MakeSphere(double r) 191 | { 192 | foreach (Dot d in this.listDot) 193 | { 194 | d.AdjustRadius(r); 195 | } 196 | } 197 | 198 | public DotMap() 199 | { 200 | 201 | } 202 | } 203 | 204 | private void CreateMap() 205 | { 206 | this.map = new DotMap(); 207 | 208 | //crée les dot 209 | for (double t = 0d; t < Math.PI * 2d; t += Math.PI * 2d / 40d) 210 | { 211 | Dot d1 = new Dot(); 212 | d1.z = 0d; 213 | d1.y = Math.Sin(t); 214 | d1.x = Math.Cos(t); 215 | this.map.listDot.Add(d1); 216 | 217 | Dot d2 = new Dot(); 218 | d2.z = Math.Sin(t); 219 | d2.y = 0d; 220 | d2.x = Math.Cos(t); 221 | this.map.listDot.Add(d2); 222 | 223 | Dot d3 = new Dot(); 224 | d3.z = Math.Cos(t); 225 | d3.y = Math.Sin(t); 226 | d3.x = 0d; 227 | this.map.listDot.Add(d3); 228 | } 229 | 230 | 231 | 232 | } 233 | 234 | #endregion 235 | 236 | 237 | 238 | 239 | private bool IsMouseLeftDown = false; 240 | private void MouseDown() 241 | { 242 | this.IsMouseLeftDown = true; 243 | this.IsAutoScroll = false; 244 | 245 | this.LastMousePos = this.MousePos; 246 | 247 | this.AnalyTimer.Start(); 248 | 249 | } 250 | private void MouseUp() 251 | { 252 | this.IsMouseLeftDown = false; 253 | 254 | Point mpos = this.MousePos; 255 | //check si l'user a donné un élant à la souris 256 | int dx = mpos.X - this.LastMousePos.X; 257 | int dy = mpos.Y - this.LastMousePos.Y; 258 | if ((dx * dx) + (dy * dy) <= 10) 259 | { 260 | this.AnalyTimer.Stop(); 261 | this.IsAutoScroll = false; 262 | } 263 | else 264 | { 265 | this.IsAutoScroll = true; 266 | 267 | if (dy > 0) { this.autoDown = dy; } else { this.autoDown = 0; } 268 | if (dy < 0) { this.autoUp = -dy; } else { this.autoUp = 0; } 269 | if (dx > 0) { this.autoRight = dx; } else { this.autoRight = 0; } 270 | if (dx < 0) { this.autoLeft = -dx; } else { this.autoLeft = 0; } 271 | 272 | 273 | } 274 | } 275 | 276 | 277 | 278 | private Point LastMousePos = new Point(-1, -1); //dernière position de la souris, la framme précédante 279 | 280 | private bool IsAutoScroll = false; //indique si l'user a donné un élant de rotation et donc que la balle continue de rouler tout seul 281 | private int autoUp = 0; 282 | private int autoDown = 0; 283 | private int autoRight = 0; 284 | private int autoLeft = 0; 285 | 286 | 287 | 288 | private Timer AnalyTimer; 289 | private void AnalyTimer_Tick(object sender, EventArgs e) 290 | { 291 | double mulfact = 0.025d; 292 | if (!this.IsAutoScroll) 293 | { 294 | Point mpos = this.MousePos; 295 | //mesure le déplacement de la souris 296 | int dx = mpos.X - this.LastMousePos.X; 297 | int dy = mpos.Y - this.LastMousePos.Y; 298 | 299 | //effectue la rotation 300 | this.map.RotateOnY((double)dx * mulfact); 301 | this.map.RotateOnX((double)dy * mulfact); 302 | 303 | //save la position de la souris 304 | this.LastMousePos = mpos; 305 | 306 | //raise l'event du scroll 307 | this.Raise_Scroll(dx, dy); 308 | 309 | } 310 | else 311 | { 312 | //rotation auto 313 | if (this.autoDown > 0) { this.map.RotateOnX((double)(this.autoDown) * mulfact); this.autoDown--; } 314 | if (this.autoUp > 0) { this.map.RotateOnX((double)(-this.autoUp) * mulfact); this.autoUp--; } 315 | if (this.autoRight > 0) { this.map.RotateOnY((double)(this.autoRight) * mulfact); this.autoRight--; } 316 | if (this.autoLeft > 0) { this.map.RotateOnY((double)(-this.autoLeft) * mulfact); this.autoLeft--; } 317 | 318 | if (this.autoDown <= 0 && this.autoUp <= 0 && this.autoRight <= 0 && this.autoLeft <= 0) { this.AnalyTimer.Stop(); } 319 | 320 | //raise l'event du scroll 321 | int dx = 0; 322 | int dy = 0; 323 | if (this.autoDown > 0) { dy = this.autoDown; } 324 | if (this.autoUp > 0) { dy = -this.autoUp; } 325 | if (this.autoRight > 0) { dx = this.autoRight; } 326 | if (this.autoLeft > 0) { dx = -this.autoLeft; } 327 | this.Raise_Scroll(dx, dy); 328 | 329 | } 330 | 331 | 332 | 333 | this.RefreshImage(); 334 | } 335 | 336 | 337 | private void CreateTimer() 338 | { 339 | this.AnalyTimer = new Timer(); 340 | this.AnalyTimer.Interval = 50; // 100 341 | this.AnalyTimer.Tick += new EventHandler(this.AnalyTimer_Tick); 342 | 343 | 344 | } 345 | 346 | 347 | 348 | 349 | 350 | private void RefreshImage() 351 | { 352 | int imgWidth = this.ImageBox.Width; 353 | int imgHeight = this.ImageBox.Height; 354 | Bitmap img = new Bitmap(imgWidth, imgHeight); 355 | Graphics g = Graphics.FromImage(img); 356 | g.Clear(Color.Blue); 357 | 358 | double UiRadius = (double)((imgWidth / 2) - 3); 359 | g.FillEllipse(Brushes.White, (imgWidth / 2) - (int)UiRadius, (imgHeight / 2) - (int)UiRadius, (int)UiRadius * 2, (int)UiRadius * 2); 360 | g.DrawEllipse(Pens.DimGray, (imgWidth / 2) - (int)UiRadius, (imgHeight / 2) - (int)UiRadius, (int)UiRadius * 2, (int)UiRadius * 2); 361 | 362 | 363 | foreach (Dot d in this.map.listDot) 364 | { 365 | //on dessine le dot seulement s'il n'est pas du côté arrière de la balle 366 | if (d.z <= 0d) 367 | { 368 | int uix = (int)(((double)imgWidth / 2d) + (d.x * UiRadius)); 369 | int uiy = (int)(((double)imgHeight / 2d) - (d.y * UiRadius)); 370 | 371 | try 372 | { 373 | //img.SetPixel(uix, uiy, Color.Black); 374 | 375 | g.FillRectangle(Brushes.Black, uix, uiy, 2, 2); 376 | 377 | } 378 | catch { } 379 | 380 | } 381 | } 382 | 383 | 384 | 385 | 386 | g.Dispose(); 387 | if (this.ImageBox.Image != null) { this.ImageBox.Image.Dispose(); } 388 | this.ImageBox.Image = img; 389 | } 390 | 391 | 392 | 393 | 394 | 395 | 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /oRollerDialog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Drawing; 7 | using System.Windows.Forms; 8 | 9 | namespace C_FormTest1 10 | { 11 | public class oRollerDialog 12 | { 13 | private Form forme; 14 | private PictureBox ImageBox; 15 | private Button btn; 16 | 17 | private Random rnd = new Random(); 18 | 19 | 20 | public string Message 21 | { 22 | get { return this.forme.Text; } 23 | set { this.forme.Text = value; } 24 | } 25 | 26 | 27 | 28 | public int Height = 400; 29 | public Font Font = new Font("consolas", 10f); 30 | public bool RotateChoice = true; 31 | public bool RandomizeChoiceRotation = true; //uniquement si RotateChoice 32 | public bool ColorsAndRotateColors = true; //uniquement si RotateChoice 33 | public List choices = new List(); //liste des choix 34 | 35 | public string Answer = ""; //variable qui contient la valeur que le dialogue retourne 36 | 37 | 38 | public void ShowDialog() 39 | { 40 | 41 | 42 | 43 | this.RefreshSize(); 44 | //image d'arrière plan 45 | this.CreateBackImg(); 46 | this.TimerFrame.Start(); 47 | 48 | 49 | //position de la fenetre 50 | Point mpos = Cursor.Position; 51 | this.forme.Top = mpos.Y - (this.forme.Height / 2); 52 | this.forme.Left = mpos.X - (this.forme.Width / 2); 53 | if (this.forme.Top < 0) { this.forme.Top = 0; } 54 | if (this.forme.Left < 0) { this.forme.Left = 0; } 55 | 56 | this.forme.ShowDialog(); 57 | 58 | } 59 | 60 | 61 | //void new() 62 | public oRollerDialog() 63 | { 64 | 65 | this.forme = new Form(); 66 | this.forme.FormBorderStyle = FormBorderStyle.FixedSingle; 67 | this.forme.MaximizeBox = false; 68 | this.forme.MinimizeBox = false; 69 | this.forme.StartPosition = FormStartPosition.Manual; 70 | 71 | 72 | this.ImageBox = new PictureBox(); 73 | this.ImageBox.Parent = this.forme; 74 | this.ImageBox.BorderStyle = BorderStyle.FixedSingle; 75 | 76 | 77 | this.btn = new Button(); 78 | this.btn.Parent = this.forme; 79 | this.btn.Text = "Submit"; 80 | this.btn.Font = new Font("consolas", 30f); 81 | this.btn.Click += new EventHandler(this.btn_Click); 82 | 83 | 84 | 85 | this.CreateTimer(); 86 | 87 | } 88 | private void btn_Click(object sender, EventArgs e) 89 | { 90 | int index = this.GetIndexOfActualAngle(); 91 | 92 | this.Answer = this.choices[index]; 93 | 94 | GC.Collect(); 95 | this.TimerFrame.Stop(); 96 | this.forme.Close(); 97 | 98 | } 99 | 100 | 101 | private void RefreshSize() 102 | { 103 | 104 | this.forme.Width = this.Height + 200; 105 | this.forme.Height = Height + 120; 106 | 107 | 108 | this.ImageBox.Location = new Point(2, 2); 109 | this.ImageBox.Width = this.forme.Width - 18 - this.ImageBox.Left; 110 | this.ImageBox.Height = this.forme.Height - 40 - 70 - this.ImageBox.Top; 111 | 112 | 113 | this.btn.Width = this.forme.Width - 19; 114 | this.btn.Location = new Point(this.ImageBox.Left, this.ImageBox.Top + this.ImageBox.Height + 2); 115 | this.btn.Height = this.forme.Height - 40 - this.btn.Top; 116 | 117 | 118 | } 119 | 120 | 121 | 122 | 123 | 124 | 125 | //quelque variable qui permettent de control les changement après un certain nombre de tick 126 | private int ChangeChoiceSpeed_MinFrameLength = 10; 127 | private int ChangeChoiceSpeed_MaxFraneLength = 20; 128 | private double ChangeChoiceSpeed_SpeedRadius = 0.02d; //valeur aléatoire maximale de la vitesse des choix, autour de 0 129 | private int zzzChangeChoiceSpeed_Left = 0; //nombre de frame restante avant le changement 130 | private double zzzChangeChoiceSpeed_Speed = -0.01d; //vitesse actuel de déplacement des choix 131 | 132 | private Color[] zzzColorArray = new Color[] { Color.Black, Color.Blue, Color.Red, Color.Green }; 133 | private int RotateColor_FrameLength = 40; //nombre de frame pour rotater les couleur de 1 134 | private int zzzRotateColor_Delta = 0; //décalage actuel des couleur 135 | private int zzzRotateColor_Left = 10; //nombre de frame restante 136 | 137 | private Timer TimerFrame; 138 | private void CreateTimer() 139 | { 140 | this.TimerFrame = new Timer(); 141 | this.TimerFrame.Interval = 1; // 5 142 | this.TimerFrame.Tick += new EventHandler(this.TimerFrame_Tick); 143 | } 144 | private void TimerFrame_Tick(object sender, EventArgs e) 145 | { 146 | //change la couleur, s'il le faut 147 | if (this.ColorsAndRotateColors) 148 | { 149 | //check et execute la rotation des couleur 150 | this.zzzRotateColor_Left--; 151 | if (this.zzzRotateColor_Left < 0) 152 | { 153 | //décale les couleur 154 | this.zzzRotateColor_Delta++; 155 | 156 | //reset les left frame 157 | this.zzzRotateColor_Left = this.RotateColor_FrameLength; 158 | } 159 | } 160 | 161 | this.ActualAngle += 0.1d; // 0.1d 162 | 163 | if (this.RotateChoice) 164 | { 165 | ////fait décaler les choix d'angle 166 | this.ActualChoiceAngle += zzzChangeChoiceSpeed_Speed; 167 | 168 | if (this.RandomizeChoiceRotation) //seulement si on veut randomizer la direction des choix, on effectue les truc en lien 169 | { 170 | ////check et execute le changement aléatoire de la vitesse des choix 171 | this.zzzChangeChoiceSpeed_Left--; 172 | if (this.zzzChangeChoiceSpeed_Left < 0) //si le nombre de frame est écoulé, on change de vitesse et on re-crinque la variable 173 | { 174 | //décide d'une nouvelle vitesse, aléatoire 175 | this.zzzChangeChoiceSpeed_Speed = -1d * this.ChangeChoiceSpeed_SpeedRadius * this.rnd.NextDouble(); 176 | 177 | //recrinque le nombre de frame restante 178 | this.zzzChangeChoiceSpeed_Left = this.rnd.Next(this.ChangeChoiceSpeed_MinFrameLength, this.ChangeChoiceSpeed_MaxFraneLength); 179 | 180 | } 181 | } 182 | } 183 | 184 | //make sure que les valeurs des angles sont dans les bound 185 | if (this.ActualAngle >= 2d * Math.PI) { this.ActualAngle -= 2d * Math.PI; } 186 | if (this.ActualAngle < 0d) { this.ActualAngle += 2d * Math.PI; } 187 | 188 | if (this.ActualChoiceAngle >= 2d * Math.PI) { this.ActualChoiceAngle -= 2d * Math.PI; } 189 | if (this.ActualChoiceAngle < 0d) { this.ActualChoiceAngle += 2d * Math.PI; } 190 | 191 | 192 | if (this.RotateChoice) 193 | { 194 | this.CreateBackImg(); 195 | } 196 | 197 | this.RefreshImage(); //refresh la flèche 198 | } 199 | 200 | 201 | 202 | //image d'arrière plan. il ne sert à rien de recrée cette image à chaque frame 203 | private Bitmap backimg = null; 204 | private void CreateBackImg() 205 | { 206 | 207 | int imgWidth = this.ImageBox.Width; 208 | int imgHeight = this.ImageBox.Height; 209 | 210 | Bitmap img = new Bitmap(this.ImageBox.Width, this.ImageBox.Height); 211 | Graphics g = Graphics.FromImage(img); 212 | g.Clear(Color.White); 213 | 214 | 215 | //dessine les point et les string associés 216 | double len = (double)(this.Height / 2) * 0.9d; // 0.9d 217 | for (int i = 0; i < this.choices.Count; i++) 218 | { 219 | double angle = this.MakeSureAngleInBound(2d * Math.PI / (double)(this.choices.Count) * (double)i + this.ActualChoiceAngle); 220 | 221 | 222 | int posx = (imgWidth / 2) + (int)(len * Math.Cos(angle)); 223 | int posy = (imgHeight / 2) - (int)(len * Math.Sin(angle)); 224 | 225 | //img.SetPixel(posx, posy, Color.Red); 226 | int cr = 5; //rayon du cercle 227 | g.FillEllipse(Brushes.Black, posx - cr, posy - cr, 2 * cr, 2 * cr); 228 | 229 | 230 | //draw the string 231 | string text = this.choices[i]; 232 | bool DrawToTheRight = angle < Math.PI / 2d || angle > 3d * Math.PI / 2d; 233 | SizeF TextSize = g.MeasureString(text, this.Font); 234 | Brush TextBrush = Brushes.Black; 235 | if (this.ColorsAndRotateColors) 236 | { 237 | TextBrush = new SolidBrush(this.zzzColorArray[(i + this.zzzRotateColor_Delta) % this.zzzColorArray.Length]); 238 | } 239 | 240 | if (DrawToTheRight) 241 | { 242 | g.DrawString(text, this.Font, TextBrush, posx + cr, posy - (int)(TextSize.Height / 2f)); 243 | } 244 | else 245 | { 246 | g.DrawString(text, this.Font, TextBrush, posx - cr - (int)(TextSize.Width), posy - (int)(TextSize.Height / 2f)); 247 | 248 | } 249 | 250 | } 251 | 252 | 253 | g.Dispose(); 254 | if (this.backimg != null) { this.backimg.Dispose(); } 255 | this.backimg = img; 256 | } 257 | 258 | private void RefreshImage() 259 | { 260 | //this.ImageBox.Image = this.backimg; 261 | 262 | 263 | int imgWidth = this.backimg.Width; 264 | int imgHeight = this.backimg.Height; 265 | 266 | //Bitmap img = new Bitmap(imgWidth, imgHeight); 267 | Bitmap img = new Bitmap(this.backimg); 268 | Graphics g = Graphics.FromImage(img); 269 | //g.DrawImage(this.backimg, 0, 0); 270 | double arrowlength = (double)(this.Height / 2) * 0.8d; // 0.8d 271 | double pointeLength = (double)(this.Height / 2) * 0.1d; 272 | 273 | 274 | 275 | double arrowWidth = arrowlength * Math.Cos(this.ActualAngle); 276 | double arrowHeight = arrowlength * Math.Sin(this.ActualAngle); 277 | 278 | double pointeWidth1 = pointeLength * Math.Sin(this.ActualAngle + (Math.PI / 4d)); 279 | double pointeHeight1 = pointeLength * Math.Cos(this.ActualAngle + (Math.PI / 4d)); 280 | double pointeWidth2 = pointeLength * Math.Sin(this.ActualAngle - (Math.PI / 4d)); 281 | double pointeHeight2 = pointeLength * Math.Cos(this.ActualAngle - (Math.PI / 4d)); 282 | 283 | ////dessine la flèche 284 | //l'arrière de la flèche 285 | g.DrawLine(Pens.Black, imgWidth / 2, imgHeight / 2, (imgWidth / 2) - (int)(arrowWidth), (imgHeight / 2) + (int)(arrowHeight)); //dessine la ligne dans le sens opposé où elle pointe 286 | 287 | //pointe 288 | g.DrawLine(Pens.Black, imgWidth / 2, imgHeight / 2, (imgWidth / 2) - (int)pointeWidth1, (imgHeight / 2) - (int)pointeHeight1); // 1 289 | g.DrawLine(Pens.Black, imgWidth / 2, imgHeight / 2, (imgWidth / 2) + (int)pointeWidth2, (imgHeight / 2) + (int)pointeHeight2); // 2 290 | 291 | 292 | g.Dispose(); 293 | if (this.ImageBox.Image != null) { this.ImageBox.Image.Dispose(); } 294 | this.ImageBox.Image = img; 295 | } 296 | 297 | 298 | private double ActualAngle = 0d; //angle actuel de la flèche RADIAN 299 | private double ActualChoiceAngle = 0d; //angle actuel qui décale les choix 300 | private int GetIndexOfActualAngle() 301 | { 302 | if (!this.RotateChoice) 303 | { 304 | double ChoiceWidth = 2d * Math.PI / (double)(this.choices.Count); //"largeur" d'un des choix 305 | 306 | //si ActualAngle est plus grand que 2pi-cwidth/2 alors il est plus proche de l'index 0 307 | if (this.ActualAngle > 2d * Math.PI - (ChoiceWidth / 2d)) { return 0; } 308 | 309 | 310 | double tempangle = this.ActualAngle + (ChoiceWidth / 2d); 311 | int index = (int)(tempangle / ChoiceWidth); 312 | 313 | //check de bound 314 | if (index < 0) { index = 0; } 315 | if (index >= this.choices.Count) { index = this.choices.Count - 1; } //ce check est nécessaire pour que cette fonction fonctionne correctement 316 | 317 | return index; 318 | } 319 | else 320 | { 321 | double ChoiceWidth = 2d * Math.PI / (double)(this.choices.Count); //"largeur" d'un des choix 322 | 323 | //angle ajusté 324 | double AngleToUse = this.MakeSureAngleInBound(this.ActualAngle - this.ActualChoiceAngle); 325 | //si le AngleToUse est plus grand que 2pi-cwidth/2 alors il est plus proche de l'index 0 326 | if (AngleToUse > 2d * Math.PI - (ChoiceWidth / 2d)) { return 0; } 327 | 328 | 329 | double tempangle = AngleToUse + (ChoiceWidth / 2d); 330 | 331 | int index = (int)(tempangle / ChoiceWidth); 332 | 333 | //check de bound 334 | if (index < 0) { index = 0; } 335 | if (index >= this.choices.Count) { index = this.choices.Count - 1; } //ce check est nécessaire pour que cette fonction fonctionne correctement 336 | 337 | return index; 338 | 339 | } 340 | //return -1; 341 | } 342 | 343 | 344 | 345 | //retourne le même angle, mais modulo 2pi 346 | private double MakeSureAngleInBound(double angle) 347 | { 348 | double rep = angle; 349 | double pi2 = 2d * Math.PI; 350 | while (rep < 0d) 351 | { 352 | rep += pi2; 353 | } 354 | while (rep >= pi2) 355 | { 356 | rep -= pi2; 357 | } 358 | return rep; 359 | } 360 | 361 | 362 | 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /o3dBallDialog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Drawing; 7 | using System.Windows.Forms; 8 | 9 | namespace C_FormTest1 10 | { 11 | public class o3dBallDialog 12 | { 13 | private Form forme; 14 | private PictureBox ImageBox; 15 | 16 | private Random rnd = new Random(); 17 | 18 | 19 | 20 | 21 | public bool StereoscopicMode = true; 22 | 23 | 24 | 25 | 26 | 27 | public string Title 28 | { 29 | get { return this.forme.Text; } 30 | set { this.forme.Text = value; } 31 | } 32 | 33 | public List Choices = new List(); 34 | public void AddChoice(string newchoice) { this.Choices.Add(newchoice); } 35 | 36 | 37 | public string Answer = ""; 38 | public bool IsCanceled = true; 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | public void ShowDialog() 47 | { 48 | 49 | //place la form autour de la souris 50 | Point mpos = Cursor.Position; 51 | this.forme.Top = mpos.Y - (this.forme.Height / 2); 52 | this.forme.Left = mpos.X - (this.forme.Width / 2); 53 | if (this.forme.Top < 0) { this.forme.Top = 0; } 54 | if (this.forme.Left < 0) { this.forme.Left = 0; } 55 | 56 | 57 | 58 | ////fin 59 | this.AnalyTimer.Start(); 60 | 61 | this.CreateMessages(); 62 | this.CreateChoices(); 63 | 64 | this.RefreshImage(); 65 | this.forme.ShowDialog(); 66 | } 67 | public o3dBallDialog() 68 | { 69 | this.forme = new Form(); 70 | this.forme.Size = new Size(600, 600); 71 | this.forme.StartPosition = FormStartPosition.Manual; 72 | this.forme.MinimizeBox = false; 73 | this.forme.MaximizeBox = false; 74 | this.forme.ShowIcon = false; 75 | this.forme.ShowInTaskbar = false; 76 | this.forme.FormBorderStyle = FormBorderStyle.FixedDialog; 77 | this.forme.Load += new EventHandler(this.forme_Load); 78 | this.forme.FormClosing += new FormClosingEventHandler(this.form1_FormClosing); 79 | 80 | 81 | this.ImageBox = new PictureBox(); 82 | this.ImageBox.Parent = this.forme; 83 | this.ImageBox.Dock = DockStyle.Fill; 84 | this.ImageBox.BackColor = Color.Blue; 85 | this.ImageBox.MouseLeave += new EventHandler(this.ImageBox_MouseLeave); 86 | this.ImageBox.MouseMove += new MouseEventHandler(this.ImageBox_MouseMove); 87 | this.ImageBox.MouseDown += new MouseEventHandler(this.ImageBox_MouseDown); 88 | 89 | 90 | 91 | 92 | ////create 93 | this.CreateTimer(); 94 | 95 | 96 | 97 | } 98 | private void forme_Load(object sender, EventArgs e) 99 | { 100 | //téléporte la souris au milieu de la form 101 | Cursor.Position = this.ImageBox.PointToScreen(new Point(this.ImageBox.Width / 2, this.ImageBox.Height / 2)); 102 | 103 | } 104 | private void form1_FormClosing(object sender, FormClosingEventArgs e) 105 | { 106 | this.AnalyTimer.Stop(); 107 | } 108 | 109 | private void ImageBox_MouseLeave(object sender, EventArgs e) 110 | { 111 | this.ImageBox.Cursor = Cursors.Arrow; 112 | } 113 | private void ImageBox_MouseMove(object sender, MouseEventArgs e) 114 | { 115 | //le curseur devient une hand seulement si la souris est dessus un message qui est un url 116 | if (e.Y <= this.uiMessageHeight) 117 | { 118 | //check si le message a un url 119 | if (this.listMessage[this.ActualMessage].url.Length > 1) 120 | { 121 | this.ImageBox.Cursor = Cursors.Hand; 122 | } 123 | else 124 | { 125 | this.ImageBox.Cursor = Cursors.Arrow; 126 | } 127 | } 128 | else 129 | { 130 | this.ImageBox.Cursor = Cursors.Arrow; 131 | } 132 | 133 | 134 | } 135 | private void ImageBox_MouseDown(object sender, MouseEventArgs e) 136 | { 137 | if (e.Button == MouseButtons.Left) 138 | { 139 | //check si l'user a clické sur la zone du message 140 | if (e.Y <= this.uiMessageHeight) 141 | { 142 | //check si le message a un url 143 | if (this.listMessage[this.ActualMessage].url.Length > 1) 144 | { 145 | System.Diagnostics.Process.Start("cmd.exe", "/c start " + this.listMessage[this.ActualMessage].url); 146 | } 147 | } 148 | else 149 | { 150 | //l'user n'a pas clické sur la zone du message 151 | 152 | Choice c = this.GetClosestChoiceToCamera(); 153 | if (c != null) 154 | { 155 | this.IsCanceled = false; 156 | this.Answer = c.value; 157 | this.AnalyTimer.Stop(); 158 | this.forme.Close(); 159 | } 160 | 161 | 162 | 163 | 164 | 165 | 166 | } 167 | } 168 | if (e.Button == MouseButtons.Right) 169 | { 170 | //shuffle 171 | foreach (Choice c in this.map.listChoice) 172 | { 173 | c.x = 2d * this.SphereRadius * this.rnd.NextDouble() - this.SphereRadius; 174 | c.y = 2d * this.SphereRadius * this.rnd.NextDouble() - this.SphereRadius; 175 | c.z = 2d * this.SphereRadius * this.rnd.NextDouble() - this.SphereRadius; 176 | c.AdjustRadius(this.SphereRadius); 177 | } 178 | } 179 | } 180 | 181 | 182 | 183 | 184 | #region choice map 185 | private double CameraZ = -4d; // -7d coordonné z de la caméra. la caméra est dans les z négatif et regarde vers les z positif 186 | private double SphereRadius = 1d; //rayon de la sphère 187 | 188 | 189 | 190 | private ChoiceMap map; 191 | 192 | private class Choice 193 | { 194 | public double x = 1d; 195 | public double y = 1d; 196 | public double z = 1d; 197 | public string value = "novalue"; //le choix donné à l'utilisateur 198 | 199 | public void AdjustRadius(double r = 1d) 200 | { 201 | double div = Math.Sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z)) / r; 202 | this.x /= div; 203 | this.y /= div; 204 | this.z /= div; 205 | } 206 | 207 | public void RotateOnY(double rad) 208 | { 209 | this.RotateOnY(Math.Sin(rad), Math.Cos(rad)); 210 | } 211 | public void RotateOnY(double sinn, double coss) 212 | { 213 | double newx = (this.x * coss) - (this.z * sinn); 214 | double newz = (this.x * sinn) + (this.z * coss); 215 | this.x = newx; 216 | this.z = newz; 217 | } 218 | public void RotateOnX(double rad) 219 | { 220 | this.RotateOnX(Math.Sin(rad), Math.Cos(rad)); 221 | } 222 | public void RotateOnX(double sinn, double coss) 223 | { 224 | double newz = (this.z * coss) - (this.y * sinn); 225 | double newy = (this.z * sinn) + (this.y * coss); 226 | this.z = newz; 227 | this.y = newy; 228 | } 229 | 230 | public Choice() { } 231 | public Choice(string StartValue) 232 | { 233 | this.value = StartValue; 234 | } 235 | public Choice(string StartValue, double sx, double sy, double sz) 236 | { 237 | this.value = StartValue; 238 | this.x = sx; 239 | this.y = sy; 240 | this.z = sz; 241 | } 242 | } 243 | private class ChoiceMap 244 | { 245 | public List listChoice = new List(); 246 | 247 | //les nombre complex sont utilisé pour effecter les rotation 248 | public void RotateOnY(double rad) 249 | { 250 | double sinn = Math.Sin(rad); 251 | double coss = Math.Cos(rad); 252 | 253 | foreach (Choice c in this.listChoice) 254 | { 255 | c.RotateOnY(sinn, coss); 256 | } 257 | } 258 | public void RotateOnX(double rad) 259 | { 260 | double sinn = Math.Sin(rad); 261 | double coss = Math.Cos(rad); 262 | 263 | foreach (Choice c in this.listChoice) 264 | { 265 | c.RotateOnX(sinn, coss); 266 | } 267 | } 268 | 269 | 270 | public void MakeSphere(double r) 271 | { 272 | foreach (Choice c in this.listChoice) 273 | { 274 | c.AdjustRadius(r); 275 | } 276 | } 277 | 278 | public ChoiceMap() 279 | { 280 | 281 | } 282 | } 283 | 284 | 285 | private Choice GetClosestChoiceToCamera() 286 | { 287 | double z = 2d * this.SphereRadius; 288 | Choice rep = null; 289 | foreach (Choice c in this.map.listChoice) 290 | { 291 | if (c.z < z) 292 | { 293 | z = c.z; 294 | rep = c; 295 | } 296 | } 297 | return rep; 298 | } 299 | //return something that has similar variable than the average dist between points 300 | private double GetTotalDist() 301 | { 302 | double sum = 0; 303 | if (this.map.listChoice.Count >= 2) 304 | { 305 | for (int i = 0; i < this.map.listChoice.Count - 1; i++) 306 | { 307 | Choice c1 = this.map.listChoice[i]; 308 | for (int j = i + 1; j < this.map.listChoice.Count; j++) 309 | { 310 | Choice c2 = this.map.listChoice[j]; 311 | //calcul la distance, mais au carrée pour éviter la racine carrée qui ne sert à rien 312 | double dx = c2.x - c1.x; 313 | double dy = c2.y - c1.y; 314 | double dz = c2.z - c1.z; 315 | double dist = (dx * dx) + (dy * dy) + (dz * dz); 316 | sum += Math.Sqrt(dist); // /!\ /!\ /!\ WARNING to anybody who would try to reproduce the code : THIS square root is very important. do not remove it for optimisation purpose because it will break the spreading of the choices around the surface. they will form packs of multiple choices very close together (they almost fusion) and these packs will kinda badly spread on the surface. 317 | } 318 | } 319 | 320 | } 321 | return sum; 322 | } 323 | 324 | 325 | private void TryToIncreaseDist_Fast(double delta) 326 | { 327 | foreach (Choice c in this.map.listChoice) 328 | { 329 | double savex = c.x; 330 | double savey = c.y; 331 | double savez = c.z; 332 | 333 | double ActualDist = this.GetTotalDist(); 334 | 335 | double reverse = -1.1d; 336 | 337 | //ont choisi une variable aléatoirment 338 | int rndvar = this.rnd.Next(0, 2); 339 | if (rndvar == 0) // x 340 | { 341 | c.RotateOnY(delta); 342 | double NewDist = this.GetTotalDist(); 343 | if (NewDist < ActualDist) 344 | { 345 | c.RotateOnY(reverse * delta); 346 | } 347 | } 348 | else // y 349 | { 350 | c.RotateOnX(delta); 351 | double NewDist = this.GetTotalDist(); 352 | if (NewDist < ActualDist) 353 | { 354 | c.RotateOnX(reverse * delta); 355 | } 356 | } 357 | 358 | } 359 | } 360 | private void TryToIncreaseDist_Fast(double delta, int loop) 361 | { 362 | for (int i = 1; i <= loop; i++) 363 | { 364 | this.TryToIncreaseDist_Fast(delta); 365 | } 366 | } 367 | 368 | 369 | 370 | private void CreateChoices() 371 | { 372 | this.map = new ChoiceMap(); 373 | 374 | 375 | foreach (string s in this.Choices) 376 | { 377 | Choice c = new Choice(s, 0, 0, 0); 378 | c.x = 2d * this.SphereRadius * this.rnd.NextDouble() - this.SphereRadius; 379 | c.y = 2d * this.SphereRadius * this.rnd.NextDouble() - this.SphereRadius; 380 | c.z = 2d * this.SphereRadius * this.rnd.NextDouble() - this.SphereRadius; 381 | c.AdjustRadius(this.SphereRadius); 382 | this.map.listChoice.Add(c); 383 | } 384 | 385 | //this.map.MakeSphere(this.SphereRadius); 386 | } 387 | #endregion 388 | #region analy timer 389 | private Timer AnalyTimer; 390 | 391 | private void AnalyTimer_Tick(object sender, EventArgs e) 392 | { 393 | //fait avancer les message 394 | this.msgTickLeft--; 395 | if (this.msgTickLeft <= 0) 396 | { 397 | if (this.ActualMsgState == msgState.Showing) 398 | { 399 | this.ActualMsgState = msgState.Show; 400 | this.msgTickLeft = this.msgtlShownTime; 401 | 402 | } 403 | else if (this.ActualMsgState == msgState.Show) 404 | { 405 | this.ActualMsgState = msgState.Hiding; 406 | this.msgTickLeft = this.msgtlShowHideTime; 407 | 408 | } 409 | else if (this.ActualMsgState == msgState.Hiding) 410 | { 411 | this.ActualMsgState = msgState.Showing; 412 | this.msgTickLeft = this.msgtlShowHideTime; 413 | this.ActualMessage = (this.ActualMessage + 1) % this.listMessage.Count; 414 | } 415 | } 416 | 417 | 418 | //fait tourner les choix selon la position de la souris 419 | Point mpos = this.ImageBox.PointToClient(Cursor.Position); 420 | //juste pour être sûr que la souris est dans l'image box 421 | int uiOutDeadZone = this.uiMessageHeight; // 10 distance au bord de l'image box dans laquelle la souris peut bouger sans déclancher de rotation 422 | if (mpos.X >= uiOutDeadZone && mpos.Y >= uiOutDeadZone && mpos.X < this.ImageBox.Width - uiOutDeadZone && mpos.Y < this.ImageBox.Height - uiOutDeadZone) 423 | { 424 | int uiDeadZone = 60; //rayon (carrée) zone au milieu dans laquel la souris peut bouger sans déclancher de rotation 425 | 426 | //obtien la position horizontale et verticale de la souris relativement au milieu 427 | int mx = mpos.X - (this.ImageBox.Width / 2); 428 | if (Math.Abs(mx) < uiDeadZone) { mx = 0; } 429 | else 430 | { 431 | //on ramène ca à une transition plus douce, qui part de 0 432 | if (mx > 0) 433 | { //à droite 434 | mx -= uiDeadZone; 435 | if (this.ImageBox.Width - uiOutDeadZone - mpos.X < mx) { mx = this.ImageBox.Width - uiOutDeadZone - mpos.X; } //transition tranquille 436 | } 437 | else //à gauche 438 | { 439 | mx += uiDeadZone; 440 | if (mpos.X - uiOutDeadZone < -mx) { mx = uiOutDeadZone - mpos.X; } //transition tranquille 441 | } 442 | } 443 | int my = mpos.Y - (this.ImageBox.Height / 2); 444 | if (Math.Abs(my) < uiDeadZone) { my = 0; } 445 | else 446 | { 447 | //on ramène ca à une transition plus douce, qui part de 0 448 | if (my > 0) 449 | { //à droite 450 | my -= uiDeadZone; 451 | if (this.ImageBox.Height - uiOutDeadZone - mpos.Y < my) { my = this.ImageBox.Height - uiOutDeadZone - mpos.Y; } //transition tranquille 452 | } 453 | else //à gauche 454 | { 455 | my += uiDeadZone; 456 | if (mpos.Y - uiOutDeadZone < -my) { my = uiOutDeadZone - mpos.Y; } //transition tranquille 457 | } 458 | } 459 | 460 | 461 | double mulval = 0.001d; 462 | this.map.RotateOnY((double)mx * mulval); 463 | this.map.RotateOnX((double)my * mulval); 464 | 465 | } 466 | 467 | 468 | if (this.map.listChoice.Count > 50) 469 | { 470 | this.TryToIncreaseDist_Fast(0.02d, 3); // 0.02d 3 471 | } 472 | else 473 | { 474 | 475 | this.TryToIncreaseDist_Fast(0.02d, 10); 476 | } 477 | 478 | 479 | //fin 480 | this.RefreshImage(); 481 | } 482 | 483 | 484 | 485 | private void CreateTimer() 486 | { 487 | this.AnalyTimer = new Timer(); 488 | this.AnalyTimer.Interval = 100; 489 | this.AnalyTimer.Tick += new EventHandler(this.AnalyTimer_Tick); 490 | 491 | } 492 | 493 | #endregion 494 | #region image 495 | private double vChoiceFontHeight = 15d; //hauteur graphique des caractère se sitant à z=0. converti en float lors de DrawString 496 | private string uiChoiceFontName = "calibri"; 497 | 498 | //alpha = 16 499 | private Brush ui3dRedBrush = new SolidBrush(Color.FromArgb(16, 255, 0, 0)); 500 | private Brush ui3dBlueBrush = new SolidBrush(Color.FromArgb(16, 0, 255, 255)); 501 | 502 | private Font StateFont = new Font("calibri", 15f); 503 | 504 | private void RefreshImage() 505 | { 506 | int imgwidth = this.ImageBox.Width; 507 | int imgheight = this.ImageBox.Height; 508 | Bitmap img = new Bitmap(imgwidth, imgheight); 509 | Graphics g = Graphics.FromImage(img); 510 | g.Clear(Color.White); 511 | 512 | 513 | double hmul = (-this.CameraZ / ((this.SphereRadius / 2d) + 0.2d)) * (double)imgwidth / 2d / 2d; //sradius/2 +qqc parce que les objet ont une largeur 514 | 515 | ////dessine tout les choix en 3d 516 | foreach (Choice c in this.map.listChoice) 517 | { 518 | double z = c.z - this.CameraZ; //la distance z qui le sépare virtuellement de la caméra 519 | 520 | //on calcul la position graphique du text 521 | int uix = (imgwidth / 2) + (int)((c.x) / z * hmul); 522 | int uiy = (imgheight / 2) - (int)((c.y) / z * hmul); 523 | 524 | 525 | //dessine le nom du choix 526 | try 527 | { 528 | Font cf = new Font(this.uiChoiceFontName, (float)(this.vChoiceFontHeight * -this.CameraZ / z)); 529 | //calcul la taille du texte 530 | SizeF sf = g.MeasureString(c.value, cf); 531 | 532 | if (!this.StereoscopicMode) 533 | { 534 | g.DrawString(c.value, cf, Brushes.Black, (float)uix - (sf.Width / 2f), (float)uiy - (sf.Height / 2f)); 535 | } 536 | else 537 | { 538 | float uiTopLeftX = (float)uix - (sf.Width / 2f); 539 | float uiTopLeftY = (float)uiy - (sf.Height / 2f); 540 | 541 | float uidecal = (float)-c.z * 10f; //c.z en négatif car c'est dans les valeur négative qu'ils sont proche de la caméra. le décalage graphique doit augmenter au fur et à mesure que ca se raproche de la caméra. 542 | 543 | for (int i = 1; i <= 30; i++) 544 | { 545 | g.DrawString(c.value, cf, this.ui3dRedBrush, uiTopLeftX - (uidecal / 2f), uiTopLeftY); 546 | 547 | g.DrawString(c.value, cf, this.ui3dBlueBrush, uiTopLeftX + (uidecal / 2f), uiTopLeftY); 548 | 549 | } 550 | 551 | } 552 | 553 | } 554 | catch { } 555 | } 556 | 557 | 558 | ////dessine le message à l'écran 559 | Message msg = this.listMessage[this.ActualMessage]; 560 | Brush brushMsg = Brushes.Black; 561 | try 562 | { 563 | if (this.ActualMsgState == msgState.Showing) 564 | { 565 | //transition 566 | Color cmsg = Color.FromArgb(msg.MsgColor.R + (this.msgTickLeft * (255 - msg.MsgColor.R) / this.msgtlShowHideTime), msg.MsgColor.G + (this.msgTickLeft * (255 - msg.MsgColor.G) / this.msgtlShowHideTime), msg.MsgColor.B + (this.msgTickLeft * (255 - msg.MsgColor.B) / this.msgtlShowHideTime)); 567 | brushMsg = new SolidBrush(cmsg); 568 | } 569 | if (this.ActualMsgState == msgState.Show) 570 | { 571 | brushMsg = new SolidBrush(msg.MsgColor); 572 | } 573 | if (this.ActualMsgState == msgState.Hiding) 574 | { 575 | //transition 576 | Color cmsg = Color.FromArgb(255 - (this.msgTickLeft * (255 - msg.MsgColor.R) / this.msgtlShowHideTime), 255 - (this.msgTickLeft * (255 - msg.MsgColor.G) / this.msgtlShowHideTime), 255 - (this.msgTickLeft * (255 - msg.MsgColor.B) / this.msgtlShowHideTime)); 577 | brushMsg = new SolidBrush(cmsg); 578 | } 579 | } 580 | catch { brushMsg = Brushes.Black; } 581 | g.DrawString(msg.msg, msg.Font, brushMsg, 1f, 1f); 582 | 583 | 584 | 585 | //indique que c'est le mode stereoscopic si c'est activé 586 | if (this.StereoscopicMode) 587 | { 588 | g.DrawString("Stereoscopic mode activated", this.StateFont, Brushes.Black, 0f, (float)imgheight - 24f); 589 | } 590 | 591 | g.Dispose(); 592 | if (this.ImageBox.Image != null) { this.ImageBox.Image.Dispose(); } 593 | this.ImageBox.Image = img; 594 | GC.Collect(); 595 | } 596 | 597 | 598 | #endregion 599 | #region messages 600 | private class Message 601 | { 602 | public string msg = "nomessage"; 603 | public Font Font = new Font("calibri", 20f); 604 | public Color MsgColor = Color.Black; 605 | public string url = ""; 606 | public Message(string StartMsg, Font StartFont, Color StartMsgColor, string StartUrl = "") 607 | { 608 | this.msg = StartMsg; 609 | this.Font = StartFont; 610 | this.MsgColor = StartMsgColor; 611 | this.url = StartUrl; 612 | } 613 | } 614 | private List listMessage = new List(); 615 | 616 | 617 | private enum msgState 618 | { 619 | Showing, //en train d'apparaitre 620 | Show, //est affiché 621 | Hiding //en train de disparaitre 622 | } 623 | 624 | private int msgTickLeft = 0; //nombre de tick restant avant la prochaine phase 625 | private int ActualMessage = 0; //message actuellement affiché 626 | private msgState ActualMsgState = msgState.Showing; 627 | 628 | private int msgtlShowHideTime = 7; // 10 nombre de tick qu'il faut pour la phase apparition et de disparition. 629 | private int msgtlShownTime = 35; // 30 nombre de tick que le message reste affiché sans changer de couleur 630 | 631 | private int uiMessageHeight = 30; // 30 vertical area reserved to the messages at the top of the image 632 | 633 | 634 | private void CreateMessages() 635 | { 636 | this.listMessage.Add(new Message("Move the mouse around to rotate the choices.", new Font("calibri", 15f), Color.Black)); 637 | this.listMessage.Add(new Message("Click with the mouse to confirm your choice.", new Font("calibri", 15f), Color.Black)); 638 | if (this.StereoscopicMode) 639 | { 640 | this.listMessage.Add(new Message("Where can i buy 3D glasses?", new Font("calibri", 15f, FontStyle.Underline), Color.Blue, "https://www.google.ca/search?q=3d+glasses+red+blue+to+buy")); 641 | } 642 | this.listMessage.Add(new Message("Press right click to shuffle.", new Font("calibri", 15f), Color.Black)); 643 | 644 | //initiale l'état actuel de message 645 | this.msgTickLeft = this.msgtlShowHideTime; 646 | this.ActualMsgState = msgState.Showing; 647 | } 648 | #endregion 649 | 650 | 651 | 652 | 653 | } 654 | } 655 | -------------------------------------------------------------------------------- /CylinderList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Windows.Forms; 5 | 6 | namespace C_FormTest1 7 | { 8 | public class clElement 9 | { 10 | private string zzzText = ""; 11 | public string Text { get { return this.zzzText; } } 12 | private double zzzVirtualTextWidth = 0d; 13 | public double VirtualTextWidth { get { return this.zzzVirtualTextWidth; } } 14 | 15 | public object Tag = null; 16 | 17 | //position horizontale de la partie gauche du texte 18 | private double zzzx = 0d; 19 | public double x { get { return this.zzzx; } } 20 | 21 | //position verticale sur la surface du cylindre 22 | private double zzzangle = 0d; 23 | public double angle { get { return this.zzzangle; } } 24 | 25 | public clElement(string sText, object sTag, double sx, double sangle, double sVirtualTextWidth) 26 | { 27 | this.zzzText = sText; 28 | this.Tag = sTag; 29 | this.zzzx = sx; 30 | this.zzzangle = sangle; 31 | this.zzzVirtualTextWidth = sVirtualTextWidth; 32 | } 33 | 34 | 35 | } 36 | 37 | public class clElementEventArgs : EventArgs 38 | { 39 | public int index = -1; 40 | public clElement Elem = null; 41 | public clElementEventArgs() { } 42 | public clElementEventArgs(clElement sElem, int sindex) 43 | { 44 | this.Elem = sElem; 45 | this.index = sindex; 46 | } 47 | } 48 | 49 | public class CylinderList 50 | { 51 | 52 | private PictureBox ImageBox; 53 | public Control Parent 54 | { 55 | get { return this.ImageBox.Parent; } 56 | set { this.ImageBox.Parent = value; } 57 | } 58 | public void SetPos(int newLeft, int newTop) 59 | { 60 | this.ImageBox.Location = new Point(newLeft, newTop); 61 | } 62 | public int Top 63 | { 64 | get { return this.ImageBox.Top; } 65 | set { this.ImageBox.Top = value; } 66 | } 67 | public int Left 68 | { 69 | get { return this.ImageBox.Left; } 70 | set { this.ImageBox.Left = value; } 71 | } 72 | public void SetSize(int newWidth, int newHeight) 73 | { 74 | this.ImageBox.Size = new Size(newWidth, newHeight); 75 | } 76 | public int Width 77 | { 78 | get { return this.ImageBox.Width; } 79 | set { this.ImageBox.Width = value; } 80 | } 81 | public int Height 82 | { 83 | get { return this.ImageBox.Height; } 84 | set { this.ImageBox.Height = value; } 85 | } 86 | public AnchorStyles Anchor 87 | { 88 | get { return this.ImageBox.Anchor; } 89 | set { this.ImageBox.Anchor = value; } 90 | } 91 | public DockStyle Dock 92 | { 93 | get { return this.ImageBox.Dock; } 94 | set { this.ImageBox.Dock = value; } 95 | } 96 | 97 | 98 | 99 | //return true s'il y a au moins 1 élément 100 | public bool AnyElement { get { return this.listElem.Count > 0; } } 101 | 102 | public clElement SelectedElement 103 | { 104 | get 105 | { 106 | if (this.AnyElement) 107 | { 108 | try 109 | { 110 | return this.listElem[this.SelectedIndex]; 111 | } 112 | catch 113 | { 114 | return null; 115 | } 116 | } 117 | else 118 | { 119 | return null; 120 | } 121 | } 122 | } 123 | 124 | 125 | 126 | private double VirtualRightEnd = 1d; //la position horizontale x de la fin du cylindre à droite. est augmenté au fur et à mesure que les éléments sont ajoutés 127 | 128 | private List listElem = new List(); 129 | public clElement AddElement(string Text, object Tag) 130 | { 131 | //calcul la position du nouveau élément 132 | double x = 0d; 133 | double angle = 0d; 134 | if (this.listElem.Count > 0) 135 | { 136 | angle = this.listElem[this.listElem.Count - 1].angle + this.ItemSpace; 137 | x = this.listElem[this.listElem.Count - 1].x + this.GetMinimumDeltaX(); 138 | } 139 | 140 | //on fait calculer la largeur virtuel de la chaine de text, pour savoir quel deltax on doit appliquer à chaques prochains élément à être ajouté pour qu'après 1 tour, ils ne chevauchent pas le texte de l'élément actuel 141 | double VirtualTextWidth = this.GetVirtualTextWidth(Text); 142 | //on prend la largeur du texte et on la divise par le nombre d'élément qui fait 1 tour complet 143 | double MinimumDeltaX = VirtualTextWidth / (double)(this.FullTurnItemCount()) + 0.02d; 144 | //on ajoute le deltax minimum actuel à la liste des deltax minimum du dernier tour 145 | this.AddToLastDeltaX(MinimumDeltaX); 146 | 147 | //calcule la position horizontale de la fin du cylindre à droite 148 | double NewElementEnd = x + VirtualTextWidth + this.CylinderBorderWidth; 149 | if (NewElementEnd > this.VirtualRightEnd) { this.VirtualRightEnd = NewElementEnd; } 150 | 151 | clElement e = new clElement(Text, Tag, x, angle, VirtualTextWidth); 152 | this.listElem.Add(e); 153 | return e; 154 | } 155 | public clElement AddElement(string Text) 156 | { 157 | return this.AddElement(Text, null); 158 | } 159 | 160 | //retire tout les élément. il vide la liste 161 | public void ClearElements() 162 | { 163 | ////on make sure que tout est à l'arrêt 164 | //on check le déplacement animé 165 | this.MoveTimer.Stop(); 166 | this.targetX = 0d; 167 | this.targetAngle = 0d; 168 | //make sure que les listes de movements sont vide 169 | while (this.animX.Count > 0) { this.animX.RemoveAt(0); } 170 | while (this.animAngle.Count > 0) { this.animAngle.RemoveAt(0); } 171 | 172 | ////autres choses 173 | //on reset la position de la caméra 174 | this.camX = 0d; 175 | this.camAngle = 0d; 176 | 177 | this.SelectedIndex = 0; 178 | this.VirtualRightEnd = 1d; 179 | 180 | //vide la liste des dernier deltax minimum 181 | while (this.listMinimumDeltaX.Count > 0) { this.listMinimumDeltaX.RemoveAt(0); } 182 | 183 | ////retire tout les élément 184 | while (this.listElem.Count > 0) { this.listElem.RemoveAt(0); } 185 | 186 | } 187 | 188 | 189 | //cette liste contient le delta x minimum à affectuer, pour tout les élément du dernier tour du cylindre, pour qu'après un autre tour complet du cylindre, la colonne suivante ne chevauche pas le texte. de la colonne précédante. 190 | private List listMinimumDeltaX = new List(); 191 | //ceci retourne combien d'item font 1 tour complet du cylindre. la valeur n'a pas besoin d'être exact, mais elle doit être supérieur ou égale à la quantité d'item qui font 1 tour complet. 192 | private int FullTurnItemCount() 193 | { 194 | return (int)(Math.PI * 2d / this.ItemSpace) + 2; 195 | } 196 | //ajoute une nouvelle valeur à la liste des deltax minimaux. prend automatiquement en charge de retirer les items qui datent d'il y a plus d'un tour 197 | private void AddToLastDeltaX(double newdeltax) 198 | { 199 | this.listMinimumDeltaX.Add(newdeltax); 200 | while (this.listMinimumDeltaX.Count > this.FullTurnItemCount()) 201 | { 202 | this.listMinimumDeltaX.RemoveAt(0); 203 | } 204 | } 205 | //retourne le deltax minimum à effectuer pour qu'après 1 tour complet, les nouveaux éléments ne chevauchent pas les anciens 206 | private double GetMinimumDeltaX() 207 | { 208 | double rep = 0d; 209 | foreach (double dx in this.listMinimumDeltaX) 210 | { 211 | if (dx > rep) { rep = dx; } 212 | } 213 | return rep; 214 | } 215 | 216 | #region measure string 217 | private Graphics zzzg = Graphics.FromImage(new Bitmap(10, 10)); 218 | private SizeF privateMeasurestring(string text, Font font) 219 | { 220 | return this.zzzg.MeasureString(text, font); 221 | } 222 | 223 | private double GetVirtualTextWidth(string text) 224 | { 225 | double imgHeight = 20d; 226 | return (double)(this.zzzg.MeasureString(text, new Font(this.uiFontName, (float)(this.uiCloseFontMulFactor) * (float)imgHeight)).Width) * (this.camZ - 1d) / imgHeight * this.ViewAngle * 2d; 227 | } 228 | 229 | #endregion 230 | 231 | 232 | 233 | public event EventHandler ElementMouseClick; 234 | public event EventHandler ElementMouseDoubleClick; 235 | private void Raise_ElementMouseClick(clElement elem, int index) 236 | { 237 | if (this.ElementMouseClick != null) 238 | { 239 | this.ElementMouseClick(this, new clElementEventArgs(elem, index)); 240 | } 241 | } 242 | private void Raise_ElementMouseDoubleClick(clElement elem, int index) 243 | { 244 | if (this.ElementMouseDoubleClick != null) 245 | { 246 | this.ElementMouseDoubleClick(this, new clElementEventArgs(elem, index)); 247 | } 248 | } 249 | 250 | 251 | 252 | 253 | public CylinderList() 254 | { 255 | this.ImageBox = new PictureBox(); 256 | this.ImageBox.BorderStyle = BorderStyle.FixedSingle; 257 | this.ImageBox.SizeChanged += new EventHandler(this.ImageBox_SizeChanged); 258 | this.ImageBox.MouseWheel += new MouseEventHandler(this.ImageBox_MouseWheel); 259 | this.ImageBox.MouseDown += new MouseEventHandler(this.ImageBox_MouseDown); 260 | this.ImageBox.MouseUp += new MouseEventHandler(this.ImageBox_MouseUp); 261 | this.ImageBox.MouseClick += new MouseEventHandler(this.ImageBox_MouseClick); 262 | this.ImageBox.MouseDoubleClick += new MouseEventHandler(this.ImageBox_MouseDoubleClick); 263 | 264 | 265 | this.CreateArrow(); 266 | this.CreateMoveTimer(); 267 | this.CreateInterface(); 268 | 269 | this.Refresh(); 270 | } 271 | private void ImageBox_SizeChanged(object sender, EventArgs e) 272 | { 273 | this.ResizeInterface(); 274 | this.Refresh(); 275 | } 276 | private void ImageBox_MouseWheel(object sender, MouseEventArgs e) 277 | { 278 | if (e.Delta > 0) 279 | { 280 | //up 281 | this.DefineSelectedIndex(this.SelectedIndex - 1); 282 | } 283 | else 284 | { 285 | //down 286 | this.DefineSelectedIndex(this.SelectedIndex + 1); 287 | } 288 | 289 | this.Refresh(); 290 | } 291 | private void ImageBox_MouseDown(object sender, MouseEventArgs e) 292 | { 293 | if (e.Button == MouseButtons.Left) 294 | { 295 | //check si la souris est sur un control 296 | if (!this.Check_MouseLeftDown()) 297 | { 298 | //la souris n'est pas sur un control 299 | 300 | 301 | 302 | 303 | } 304 | this.Refresh(); 305 | } 306 | if (e.Button == MouseButtons.Right) 307 | { 308 | 309 | } 310 | } 311 | private void ImageBox_MouseUp(object sender, MouseEventArgs e) 312 | { 313 | if (e.Button == MouseButtons.Left) 314 | { 315 | this.Check_MouseLeftUp(); 316 | 317 | this.Refresh(); 318 | } 319 | if (e.Button == MouseButtons.Right) 320 | { 321 | 322 | 323 | 324 | } 325 | } 326 | private void ImageBox_MouseClick(object sender, MouseEventArgs e) 327 | { 328 | //make sure que la souris n'est pas sur un control 329 | if (!this.IsMouseOnAnyControl()) 330 | { 331 | //make sure qu'il y a des élément 332 | if (this.AnyElement) 333 | { 334 | //on récupère l'élément et raise l'event 335 | clElement elem = this.listElem[this.SelectedIndex]; 336 | this.Raise_ElementMouseClick(elem, this.SelectedIndex); 337 | } 338 | } 339 | } 340 | private void ImageBox_MouseDoubleClick(object sender, MouseEventArgs e) 341 | { 342 | //make sure que la souris n'est pas sur un control 343 | if (!this.IsMouseOnAnyControl()) 344 | { 345 | //make sure qu'il y a des élément 346 | if (this.AnyElement) 347 | { 348 | //on récupère l'élément et raise l'event 349 | clElement elem = this.listElem[this.SelectedIndex]; 350 | this.Raise_ElementMouseDoubleClick(elem, this.SelectedIndex); 351 | } 352 | } 353 | } 354 | 355 | 356 | 357 | #region 3D et 2D 358 | 359 | /* 360 | * y 361 | * | 362 | * | 363 | * | 364 | * +----------------------x 365 | * / camX 366 | * / 367 | * / positive value of camAngle make the camera go down in this direction \/ down. it makes the camera rotate around the cylinder 368 | * z camZ 369 | * 370 | * camZ reste CONSTANT. le côté du cylindre le plus proche de la caméra est donc lorsque z = 1 et y = 0. z=1 parce que le cylindre a un rayon de 1. 371 | * camX NE reste PAS constant 372 | * 373 | * la caméra reste toujours à y=0 donc il n'y a pas de variable mit en place pour ajuster le y de la caméra. 374 | * 375 | * deplus, le cylindre a un rayon de 1. 376 | */ 377 | 378 | //le cylindre a un rayon de 1u donc si la position de la caméra est défini, il ne reste aucune variable à faire. 379 | //cette valeur ne change pas au cours de l'exécution. 380 | private double camZ = 10d; 381 | private double camX = 0d; //position horizontale actuel de la caméra, au milieux de l'image. cette valeur change pendant l'exécution. 382 | private double camAngle = 0d; //angle verticale de la caméra autour du cylindre. 383 | 384 | /* --------__________ 385 | * --------- / \ 386 | * view Angle here--------- / \ 387 | * ---------- | | 388 | * cam z------------------------------ | radius=1 | 389 | * 390 | * peut importe la taille graphique de ImageBox ou les paramètres virtuels de la scène en 3d, l'image affiché à l'user doit toujours être zoomé Correctement pour 391 | * que la parti visible du haut et du bas du cylindre soient ajusté pour être en haut et en bas de l'image, avec juste un très petit espacement pour gaspiller 392 | * le moins possible l'espace disponible. 393 | * 394 | * la vision fonctionnant avec des angles, il calculer l'angle que prend le cylindre dans le champs de vision, et puisqu'on met la direction dans laquel on 395 | * regarde au milieu de l'image, il faut calculer la moitier de l'angle que prend le cylindre dans le champs de vision. la moitier supérieur ou inférieur 396 | * du champs de vision forme un triangle rectangle avec le rayon du cylindre et l'axe z. le "view angle" est alors arcsin(1 / camz). la variable view angle 397 | * représente l'angle que prend dans le champs de vision la moitier supérieur du cylindre. 398 | * 399 | * il ne faut pas oublier qu'après tout cela, on n'utilise pas l'arctengente pour calculer la position d'un point à l'image, on utilise l'aprox atan(y/z) = y/z. 400 | * cela signifie que concrètement, view angle représentera le rapport y/z maximale pour lequel le point est encore visible à l'écran, et non pas un angle. 401 | * ce ne sera pas une très grande différence lorsqu'on effectura le calcul d'un point à l'image, mais on risque de se perdre dans les formules si on oublie cela. 402 | * 403 | */ 404 | private double ViewAngle 405 | { 406 | get 407 | { 408 | return Math.Asin(1d / this.camZ) + 0.04d; //+delta pour qu'il y ait un petit espacement entre le haut et la bas du cylindre et les bords de l'image 409 | } 410 | } 411 | 412 | 413 | private double ItemSpace = 2d * Math.PI / 16.7d; //angle, à partir du milieux du cylindre, qui sépare verticalement chaques éléments _\ 414 | 415 | 416 | private double uiCloseFontMulFactor = 0.1d; //pour obtenir la taille "em" graphique de la font pour une chaine de texte virtuel situé le plus proche possible de la caméra (à une distance de (this.camz - 1d)), il faut prendre la hauteur de l'image et la multiplier par cette valeur 417 | private string uiFontName = "consolas"; 418 | 419 | private Font uiBtnFont = new Font("consolas", 10f); 420 | 421 | private Brush CylinderBrush = Brushes.LightGray; 422 | private double CylinderBorderWidth = 0.5d; //largeur suplémentaire aux extrémités gauche et droite 423 | 424 | 425 | 426 | 427 | //un point avec des coordonnée 3d et une position 2d d'integer 428 | private struct p3d2d 429 | { 430 | //graphique 431 | public int uix; 432 | public int uiy; 433 | //virtuel 434 | public double x; 435 | public double y; 436 | public double z; 437 | 438 | public p3d2d(int suix, int suiy) 439 | { 440 | this.uix = suix; 441 | this.uiy = suiy; 442 | this.x = 0d; 443 | this.y = 0d; 444 | this.z = 0d; 445 | } 446 | 447 | } 448 | 449 | 450 | //[0]=z [1]=y il retourne un array de double d'une taille 2 451 | //effectue une rotation de coordonné le long de l'axe x, dans la direction de z+ vers y+ 452 | private double[] RotateOnX(double y, double z, double coss, double sinn) 453 | { 454 | return new double[] { (z * coss) - (y * sinn), (z * sinn) + (y * coss) }; 455 | } 456 | private double[] RotateOnX(double y, double z, double angle) 457 | { 458 | return this.RotateOnX(y, z, Math.Cos(angle), Math.Sin(angle)); 459 | } 460 | 461 | //cette fonction convertie une coordonné virtuel en sa position dans l'image, et sa position virtuel relative à la caméra, en prenant également en considération l'angle verticale actuel de la caméra. 462 | private p3d2d convVirtualToUI(double x, double y, double z, int imgWidth, int imgHeight, double coss, double sinn) 463 | { 464 | double MulFactor = (double)imgHeight / 2d / this.ViewAngle; //il suffit de multiplier un angle/rapport par cette valeur pour connaitre en pixel la distance graphique qu'il représente à l'image 465 | 466 | //il faut effectuer une rotation sur l'axe x, dans la direction de z+ vers y+, et d'un angle de rotation étant this.camAngle. 467 | //la multiplication complexe est utilisé pour effectuer cette rotation. 468 | //double zz = (z * coss) - (y * sinn); 469 | //double yy = (z * sinn) + (y * coss); 470 | double[] rotated = this.RotateOnX(y, z, coss, sinn); 471 | double zz = rotated[0]; 472 | double yy = rotated[1]; 473 | 474 | double dist = this.camZ - zz; //il faut calculer la distance z (z relatif à la caméra) entre le point et la caméra, qui a changé à cause de la rotation sur l'axe x 475 | double deltaX = x - this.camX; 476 | double deltaY = yy; 477 | 478 | p3d2d rep = new p3d2d(0, 0); 479 | rep.uix = (int)(((double)imgWidth / 2d) + (deltaX / dist * MulFactor) + 0.5d); //+0.5d pour l'arrondir correctement 480 | rep.uiy = (int)(((double)imgHeight / 2d) - (deltaY / dist * MulFactor) + 0.5d); 481 | rep.x = deltaX; 482 | rep.y = yy; 483 | rep.z = zz; 484 | 485 | return rep; 486 | } 487 | 488 | #endregion 489 | 490 | 491 | public void Refresh() 492 | { 493 | int imgWidth = this.Width; 494 | int imgHeight = this.Height; 495 | if (imgWidth < 100) { imgWidth = 100; } 496 | if (imgHeight < 100) { imgHeight = 100; } 497 | Bitmap img = new Bitmap(imgWidth, imgHeight); 498 | Graphics g = Graphics.FromImage(img); 499 | g.Clear(Color.White); 500 | 501 | double MulFactor = (double)imgHeight / 2d / this.ViewAngle; 502 | 503 | //ces 2 valeurs sont utilisées pour effectuer une rotation des coordonnés sur le cylindre 504 | double coss = Math.Cos(this.camAngle); 505 | double sinn = Math.Sin(this.camAngle); 506 | 507 | 508 | ////dessine le cylindre 509 | int uicTopSpace = (int)((this.ViewAngle - (1d / this.camZ)) * MulFactor); //espacement verticale entre le cylindre et les bords de l'image en haut et en bas 510 | int uicHeight = imgHeight - uicTopSpace - uicTopSpace; //hauteur graphique du cylindre 511 | 512 | //position graphique de la gauche du cylindre 513 | int uicLeft = (imgWidth / 2) - (int)((this.CylinderBorderWidth + this.camX) / this.camZ * MulFactor); 514 | if (uicLeft < 0) { uicLeft = 0; } 515 | 516 | //position graphique de la droite du cylindre 517 | int uicRight = (imgWidth / 2) + (int)((this.VirtualRightEnd - this.camX) / this.camZ * MulFactor); 518 | 519 | //dessine le corps principale du cylindre 520 | g.FillRectangle(this.CylinderBrush, uicLeft, uicTopSpace, uicRight - uicLeft, uicHeight); 521 | //dessine le bord gauche s'il le faut 522 | if (uicLeft > 0) 523 | { 524 | //calcule la position graphique du bord gauche, mais la position du cercle qui est la plus proche de la caméra 525 | int uicCloseLeft = (imgWidth / 2) - (int)((this.CylinderBorderWidth + this.camX) / (this.camZ - 1d) * MulFactor); 526 | g.FillEllipse(this.CylinderBrush, uicCloseLeft, uicTopSpace, 2 * (uicLeft - uicCloseLeft), uicHeight); 527 | } 528 | //dessine le bord droit s'il le faut 529 | if (uicRight < imgWidth) 530 | { 531 | //calcule la position graphique du bord droit, mais la position du cercle qui est la plus proche de la caméra 532 | int uicCloseRight = (imgWidth / 2) + (int)((this.VirtualRightEnd - this.camX) / (this.camZ - 1d) * MulFactor); 533 | g.FillEllipse(this.CylinderBrush, 2 * uicRight - uicCloseRight, uicTopSpace, 2 * (uicCloseRight - uicRight), uicHeight); 534 | } 535 | 536 | 537 | 538 | //si l'élément actuellement sélectionné est dans l'image (ca devrait être presque toujours le cas), cette variable sera true et le p3d2d sera gardé dans l'autre variable 539 | bool SelectedElemFound = false; //devient true si l'élément actuel est dans l'image 540 | clElement SelectedElem = null; 541 | p3d2d SelectedPos = new p3d2d(0, 0); 542 | 543 | double minimumZ = this.ViewAngle; // coordonné z+ minimum pour qu'un point de la surface du cylindre soit sur la face visible par la caméra 544 | 545 | ////dessine les élément 546 | int index = 0; 547 | while (index < this.listElem.Count) 548 | { 549 | clElement elem = this.listElem[index]; 550 | 551 | //on doit d'abord obtenir ses coordonnés réel. 552 | double elemX = elem.x; 553 | double elemY = -Math.Sin(elem.angle); 554 | double elemZ = Math.Cos(elem.angle); 555 | 556 | //maintenant on obtien ses coordonnés relatives à la caméra 557 | p3d2d relpos = this.convVirtualToUI(elemX, elemY, elemZ, imgWidth, imgHeight, coss, sinn); 558 | double dist = this.camZ - relpos.z; 559 | 560 | //check s'il sort de l'écran. si c'est le cas, on peut arrêter car tout les suivant seront eux aussi dehors de l'image 561 | if (relpos.uix > imgWidth) { break; } 562 | 563 | //check si l'item est actuellement visible à l'écran en vérifiant si le côté droit du texte est dans l'image 564 | int uiWidth = (int)(elem.VirtualTextWidth / dist * MulFactor); 565 | if (relpos.uix + uiWidth >= 0) 566 | { 567 | 568 | //si on est sur l'élément sélectionné par l'user, on le garde de côté 569 | if (index == this.SelectedIndex) 570 | { 571 | SelectedElemFound = true; 572 | SelectedElem = elem; 573 | SelectedPos = relpos; 574 | } 575 | 576 | //on check si l'élément est du côté visible du cylindre 577 | if (relpos.z > minimumZ) 578 | { 579 | 580 | //on calcul l'applatissement verticale du text. 581 | float TextYScale = (float)(relpos.z); 582 | 583 | //on calcule le height que la font doit avoir 584 | Font elemFont = new Font(this.uiFontName, (float)((double)imgHeight * this.uiCloseFontMulFactor * (this.camZ - 1d) / dist)); 585 | //on calcule la taille graphique qu'aura le text 586 | SizeF elemTextSize = g.MeasureString(elem.Text, elemFont); 587 | //on dessine le text de l'élément, centré verticalement 588 | g.TranslateTransform((float)(relpos.uix), (float)(relpos.uiy) - (elemTextSize.Height * TextYScale / 2f)); 589 | g.ScaleTransform(1f, TextYScale); 590 | g.DrawString(elem.Text, elemFont, Brushes.Black, 0f, 0f); 591 | g.ResetTransform(); 592 | 593 | //g.DrawString(elem.Text, elemFont, Brushes.Black, (float)(relpos.uix), (float)(relpos.uiy) - (elemTextSize.Height / 2f)); 594 | elemFont.Dispose(); 595 | 596 | } 597 | } 598 | 599 | //next iteration 600 | index++; 601 | } 602 | 603 | //si l'élément actuellement sélectionné a été rencontré, on dessine la flèche bleu à côté 604 | if (SelectedElemFound) 605 | { 606 | //make sure que l'élément actuel est sur la face visible 607 | if (SelectedPos.z > minimumZ) 608 | { 609 | int arrowHeight = (int)((double)(this.imgArrow.Height) * SelectedPos.z); 610 | if (arrowHeight >= this.imgArrow.Height - 5) { arrowHeight = this.imgArrow.Height; } 611 | 612 | g.DrawImage(this.imgArrow, SelectedPos.uix - 3 - this.imgArrow.Width, SelectedPos.uiy - (arrowHeight / 2), this.imgArrow.Width, arrowHeight); 613 | } 614 | } 615 | 616 | ////dessine les composant de l'interface graphique 617 | foreach (uiButton b in this.listButton) 618 | { 619 | Brush BackBrush = Brushes.Silver; 620 | if (b.IsMouseLeftDown) { BackBrush = Brushes.White; } 621 | 622 | g.FillRectangle(BackBrush, b.rec); 623 | g.DrawRectangle(Pens.Black, b.rec); 624 | 625 | //dessine le text du button, au milieux 626 | SizeF btnTextSizeF = g.MeasureString(b.Text, this.uiBtnFont); 627 | g.DrawString(b.Text, this.uiBtnFont, Brushes.Black, (float)(b.Left + (b.Width / 2)) - (btnTextSizeF.Width / 2f), (float)(b.Top + (b.Height / 2)) - (btnTextSizeF.Height / 2f)); 628 | 629 | } 630 | 631 | 632 | 633 | g.Dispose(); 634 | if (this.ImageBox.Image != null) { this.ImageBox.Image.Dispose(); } 635 | this.ImageBox.Image = img; 636 | this.ImageBox.Refresh(); 637 | 638 | } 639 | 640 | 641 | 642 | #region déplacement et élément actuellement sélectionné 643 | 644 | private int SelectedIndex = 0; //index de l'élément actuellement sélectionné 645 | 646 | private void DefineSelectedIndex(int newindex) 647 | { 648 | //make sure qu'il y a des élément. sinon on make sure que l'index actuel est 0 et que la caméra est à sa position de départ 649 | if (this.AnyElement) 650 | { 651 | int ni = newindex; 652 | //on check les bounds 653 | if (ni < 0) { ni = 0; } 654 | if (ni >= this.listElem.Count) { ni = this.listElem.Count - 1; } 655 | 656 | //on check s'il est différent de celui délà sélectionner 657 | if (ni != this.SelectedIndex) 658 | { 659 | this.SelectedIndex = ni; 660 | //on obtient l'élément et on lance le déplacement animé jusqu'à cet élément 661 | clElement elem = this.listElem[ni]; 662 | this.TargetPosition(elem.x, elem.angle); 663 | } 664 | } 665 | else //s'il n'y a aucun élément 666 | { 667 | this.SelectedIndex = 0; 668 | this.camX = 0d; 669 | this.camAngle = 0d; 670 | } 671 | } 672 | 673 | //la position finale du déplacement 674 | private double targetX = 0d; 675 | private double targetAngle = 0d; 676 | 677 | private void TargetPosition(double tX, double tAngle) 678 | { 679 | //on save la "destination" de la caméra 680 | this.targetX = tX; 681 | this.targetAngle = tAngle; 682 | 683 | //on prépare la liste des movements 684 | //on commence par make sure que les liste sont vide 685 | while (this.animX.Count > 0) { this.animX.RemoveAt(0); } 686 | while (this.animAngle.Count > 0) { this.animAngle.RemoveAt(0); } 687 | 688 | int TotalDiv = 30; 689 | double deltaAngle = this.targetAngle - this.camAngle; 690 | double deltaX = this.targetX - this.camX; 691 | for (int i = 1; i < TotalDiv; i++) 692 | { 693 | //x comprit dans [0, 1] 694 | double P1(double x) 695 | { 696 | return (-8d * x * x * x) + (12d * x * x) + (-3d * x); 697 | } 698 | this.animX.Add(this.camX + (deltaX * (double)i / (double)TotalDiv)); 699 | 700 | //x comprit dans [0, 1] 701 | double P2(double x) 702 | { 703 | return (-1.5d * x * x * x) + (2.2d * x * x) + (0.3d * x); 704 | } 705 | this.animAngle.Add(this.camAngle + (deltaAngle * P2((double)i / (double)TotalDiv))); 706 | } 707 | 708 | 709 | //on démare le timer de l'animation 710 | this.MoveTimer.Start(); 711 | } 712 | 713 | //pendant un déplacement, ces listes contiennent toutes les positions que le movetimer doit donner à la caméra. ces 2 listes ont toujours la même quantité d'objet à l'intérieur. 714 | //lorsque le timer a vidé les 2 listes, il make sure une dernière fois que la caméra est à la target position et le timer s'arrête. 715 | private List animX = new List(); 716 | private List animAngle = new List(); 717 | 718 | private Timer MoveTimer; 719 | private void CreateMoveTimer() 720 | { 721 | this.MoveTimer = new Timer(); 722 | this.MoveTimer.Interval = 50; 723 | this.MoveTimer.Tick += new EventHandler(this.MoveTimer_Tick); 724 | } 725 | private void MoveTimer_Tick(object sender, EventArgs e) 726 | { 727 | //on check s'il y a une autre position à faire 728 | if (this.animX.Count > 0) 729 | { 730 | //puisqu'il y a une autre position à faire, on la fait 731 | this.camX = this.animX[0]; 732 | this.camAngle = this.animAngle[0]; 733 | this.animX.RemoveAt(0); 734 | this.animAngle.RemoveAt(0); 735 | this.Refresh(); 736 | 737 | } 738 | else //l'animation est terminé alors on met la caméra à sa position finale et on arrête le timer 739 | { 740 | this.MoveTimer.Stop(); 741 | this.camX = this.targetX; 742 | this.camAngle = this.targetAngle; 743 | this.Refresh(); 744 | } 745 | 746 | 747 | } 748 | 749 | 750 | private Bitmap imgArrow; 751 | private void CreateArrow() 752 | { 753 | int imgWidth = 50; // 30 754 | Bitmap img = new Bitmap(imgWidth, imgWidth); 755 | Graphics g = Graphics.FromImage(img); 756 | //g.Clear(Color.DimGray); 757 | g.Clear(Color.Transparent); 758 | 759 | 760 | int w = imgWidth; 761 | int wd2 = imgWidth / 2; 762 | int wd4 = (int)((double)(imgWidth) / 3.5d); 763 | int wd6 = imgWidth / 6; 764 | int wd8 = imgWidth / 8; 765 | 766 | Point[] plArrow = new Point[] { new Point(w - 1, wd2), new Point(wd2 - 1, 0), new Point(wd2 - 1, wd4), new Point(0, wd4), new Point(0, w - wd4), new Point(wd2 - 1, w - wd4), new Point(wd2 - 1, w) }; 767 | 768 | Point[] plArrowUp = new Point[] { new Point(w - 1, wd2), new Point(wd2 - 1, 0), new Point(wd2 - 1, wd4), new Point(0, wd4), new Point(0, wd2) }; 769 | Point[] plArrowDown = new Point[] { new Point(w - 1, wd2), new Point(0, wd2), new Point(0, w - wd4), new Point(wd2 - 1, w - wd4), new Point(wd2 - 1, w) }; 770 | Point[] plArrowMiddle = new Point[] { new Point(w - 1, wd2), new Point(wd2 + 1, wd6), new Point(wd2 + 1, wd4 + wd8), new Point(0, wd4 + wd8), new Point(0, w - wd4 - wd8), new Point(wd2 + 1, w - wd4 - wd8), new Point(wd2 + 1, w - wd6) }; 771 | 772 | g.FillPolygon(Brushes.SteelBlue, plArrowUp); 773 | g.FillPolygon(Brushes.CornflowerBlue, plArrowDown); 774 | g.FillPolygon(Brushes.DodgerBlue, plArrowMiddle); 775 | g.DrawPolygon(Pens.White, plArrow); 776 | 777 | g.Dispose(); 778 | this.imgArrow = img; 779 | } 780 | 781 | #endregion 782 | 783 | #region UI 784 | 785 | private uiButton interBtnUp1; 786 | private uiButton interBtnDown1; 787 | private uiButton interBtnUp10; 788 | private uiButton interBtnDown10; 789 | 790 | private void CreateInterface() 791 | { 792 | Size btnSize = new Size(70, 25); 793 | 794 | this.interBtnUp1 = new uiButton(this); 795 | this.interBtnUp1.SetSize(btnSize); 796 | this.interBtnUp1.Text = "up"; 797 | this.interBtnUp1.MouseLeftDown += new EventHandler(this.interBtnUp1_MouseLeftDown); 798 | 799 | this.interBtnDown1 = new uiButton(this); 800 | this.interBtnDown1.SetSize(btnSize); 801 | this.interBtnDown1.Text = "down"; 802 | this.interBtnDown1.MouseLeftDown += new EventHandler(this.interBtnDown1_MouseLeftDown); 803 | 804 | this.interBtnUp10 = new uiButton(this); 805 | this.interBtnUp10.SetSize(btnSize); 806 | this.interBtnUp10.Text = "up 10"; 807 | this.interBtnUp10.MouseLeftDown += new EventHandler(this.interBtnUp10_MouseLeftDown); 808 | 809 | this.interBtnDown10 = new uiButton(this); 810 | this.interBtnDown10.SetSize(btnSize); 811 | this.interBtnDown10.Text = "down 10"; 812 | this.interBtnDown10.MouseLeftDown += new EventHandler(this.interBtnDown10_MouseLeftDown); 813 | 814 | this.ResizeInterface(); 815 | } 816 | private void ResizeInterface() 817 | { 818 | this.interBtnUp1.Left = 2; 819 | this.interBtnUp1.Top = (this.Height / 2) - 1 - this.interBtnUp1.Height; 820 | 821 | this.interBtnDown1.Left = this.interBtnUp1.Left; 822 | this.interBtnDown1.Top = this.interBtnUp1.Top + this.interBtnUp1.Height + 2; 823 | 824 | this.interBtnUp10.Left = this.interBtnUp1.Left; 825 | this.interBtnUp10.Top = this.interBtnUp1.Top - 2 - this.interBtnUp10.Height; 826 | 827 | this.interBtnDown10.Left = this.interBtnDown1.Left; 828 | this.interBtnDown10.Top = this.interBtnDown1.Top + this.interBtnDown1.Height + 2; 829 | 830 | } 831 | 832 | private void interBtnUp1_MouseLeftDown(object sender, EventArgs e) 833 | { 834 | this.DefineSelectedIndex(this.SelectedIndex - 1); 835 | } 836 | private void interBtnDown1_MouseLeftDown(object sender, EventArgs e) 837 | { 838 | this.DefineSelectedIndex(this.SelectedIndex + 1); 839 | } 840 | private void interBtnUp10_MouseLeftDown(object sender, EventArgs e) 841 | { 842 | this.DefineSelectedIndex(this.SelectedIndex - 10); 843 | } 844 | private void interBtnDown10_MouseLeftDown(object sender, EventArgs e) 845 | { 846 | this.DefineSelectedIndex(this.SelectedIndex + 10); 847 | } 848 | 849 | 850 | 851 | 852 | private Point MousePos { get { return this.ImageBox.PointToClient(Cursor.Position); } } 853 | private Rectangle MouseRec { get { return new Rectangle(this.MousePos, new Size(1, 1)); } } 854 | 855 | private List listButton = new List(); 856 | 857 | //retourne true si la souris est mouse left down sur un controle et effectue la chaine pour faire caller les event. 858 | //retourne false si la souris est dans le vide 859 | private bool Check_MouseLeftDown() 860 | { 861 | Rectangle mrec = this.MouseRec; 862 | foreach (uiButton b in this.listButton) 863 | { 864 | if (b.rec.IntersectsWith(mrec)) 865 | { 866 | b.Check_MouseLeftDown(); 867 | return true; 868 | } 869 | } 870 | 871 | return false; 872 | } 873 | 874 | private void Check_MouseLeftUp() 875 | { 876 | foreach (uiButton b in this.listButton) 877 | { 878 | b.Check_MouseLeftUp(); 879 | } 880 | } 881 | 882 | //retourne si la souris est sur un control 883 | private bool IsMouseOnAnyControl() 884 | { 885 | Rectangle mrec = this.MouseRec; 886 | foreach (uiButton b in this.listButton) 887 | { 888 | if (b.rec.IntersectsWith(mrec)) 889 | { 890 | return true; 891 | } 892 | } 893 | return false; 894 | } 895 | 896 | 897 | 898 | //s'ajoute automatiquement à son parent 899 | private class uiButton 900 | { 901 | public bool IsMouseLeftDown = false; //indique si mouse left est down sur this 902 | 903 | public CylinderList Parent = null; 904 | public Rectangle rec = new Rectangle(0, 0, 100, 30); 905 | public int Top 906 | { 907 | get { return this.rec.Y; } 908 | set { this.rec.Y = value; } 909 | } 910 | public int Left 911 | { 912 | get { return this.rec.X; } 913 | set { this.rec.X = value; } 914 | } 915 | public void SetSize(int newWidth, int newHeight) 916 | { 917 | this.Width = newWidth; 918 | this.Height = newHeight; 919 | } 920 | public void SetSize(Size newSize) 921 | { 922 | this.Width = newSize.Width; 923 | this.Height = newSize.Height; 924 | } 925 | public int Width 926 | { 927 | get { return this.rec.Width; } 928 | set { this.rec.Width = value; } 929 | } 930 | public int Height 931 | { 932 | get { return this.rec.Height; } 933 | set { this.rec.Height = value; } 934 | } 935 | 936 | public string Text = "notext"; 937 | 938 | public uiButton(CylinderList sParent) 939 | { 940 | this.Parent = sParent; 941 | sParent.listButton.Add(this); 942 | 943 | } 944 | 945 | 946 | public event EventHandler MouseLeftDown; 947 | private void Raise_MouseLeftDown() 948 | { 949 | if (this.MouseLeftDown != null) 950 | { 951 | this.MouseLeftDown(this, new EventArgs()); 952 | } 953 | } 954 | 955 | 956 | //ceci est callé par l'extérieur seulement si la souris est bel et bien sur this lors du mouse left down. 957 | public void Check_MouseLeftDown() 958 | { 959 | this.IsMouseLeftDown = true; 960 | this.Raise_MouseLeftDown(); 961 | } 962 | 963 | //ceci est callé par l'extérieur lors de mouse left up, peut importe la position de la souris 964 | public void Check_MouseLeftUp() 965 | { 966 | this.IsMouseLeftDown = false; 967 | 968 | } 969 | 970 | } 971 | 972 | #endregion 973 | 974 | 975 | } 976 | } 977 | -------------------------------------------------------------------------------- /TreeView4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using System.Drawing; 8 | 9 | namespace FileExplorer4DirectionTreeView 10 | { 11 | public class TreeView4 12 | { 13 | private Point MousePos { get { return this.ImageBox.PointToClient(Cursor.Position); } } 14 | 15 | private PictureBox ImageBox; 16 | 17 | public Control Parent 18 | { 19 | get { return this.ImageBox.Parent; } 20 | set { this.ImageBox.Parent = value; } 21 | } 22 | public int Top 23 | { 24 | get { return this.ImageBox.Top; } 25 | set { this.ImageBox.Top = value; } 26 | } 27 | public int Left 28 | { 29 | get { return this.ImageBox.Left; } 30 | set { this.ImageBox.Left = value; } 31 | } 32 | public int Width 33 | { 34 | get { return this.ImageBox.Width; } 35 | set { this.ImageBox.Width = value; } 36 | } 37 | public int Height 38 | { 39 | get { return this.ImageBox.Height; } 40 | set { this.ImageBox.Height = value; } 41 | } 42 | public AnchorStyles Anchor 43 | { 44 | get { return this.ImageBox.Anchor; } 45 | set { this.ImageBox.Anchor = value; } 46 | } 47 | public DockStyle Dock 48 | { 49 | get { return this.ImageBox.Dock; } 50 | set { this.ImageBox.Dock = value; } 51 | } 52 | 53 | 54 | 55 | 56 | #region gestion de l'arbre 57 | 58 | private TreeObject Root = null; 59 | private TreeObject ActualElement = null; //élément actuellement sélectionné par l'user 60 | 61 | 62 | 63 | 64 | private enum TOType 65 | { 66 | folder, 67 | file 68 | } 69 | private enum Dir 70 | { 71 | DownLeft, 72 | DownRight, 73 | UpRight, 74 | UpLeft 75 | } 76 | private class TreeObject 77 | { 78 | private TreeView4 TV = null; 79 | 80 | public TreeObject Parent = null; 81 | public Dir dir = Dir.DownLeft; 82 | public void SetThisAsNextDirectionOf(Dir d) 83 | { 84 | if (d == Dir.DownLeft) { this.dir = Dir.DownRight; } 85 | if (d == Dir.DownRight) { this.dir = Dir.UpRight; } 86 | if (d == Dir.UpRight) { this.dir = Dir.UpLeft; } 87 | if (d == Dir.UpLeft) { this.dir = Dir.DownLeft; } 88 | } 89 | 90 | public TOType tType = TOType.file; 91 | public bool IsFolder { get { return this.tType == TOType.folder; } } 92 | public string Path = ""; 93 | public string UiName = ""; 94 | 95 | 96 | //folder properties 97 | public bool IsOpen = false; //indique si le dossier est actuellement ouvert ou fermé à l'écran 98 | public bool ChildLoaded = false; //indique si les élément enfant de this ont été chargé dans la liste des enfant de this 99 | public List listChild = new List(); 100 | 101 | public void LoadChild() 102 | { 103 | if (this.IsFolder && !this.ChildLoaded) 104 | { 105 | //load les dossier 106 | try 107 | { 108 | string[] arFolders = System.IO.Directory.GetDirectories(this.Path); 109 | foreach (string FolderPath in arFolders) 110 | { 111 | TreeObject newto = new TreeObject(this, FolderPath, TOType.folder, this.TV); 112 | } 113 | 114 | //load les fichier 115 | string[] arFiles = System.IO.Directory.GetFiles(this.Path); 116 | foreach (string FilePath in arFiles) 117 | { 118 | TreeObject newto = new TreeObject(this, FilePath, TOType.file, this.TV); 119 | } 120 | } 121 | catch { } 122 | 123 | this.ChildLoaded = true; 124 | } 125 | } 126 | public void Open() 127 | { 128 | if (this.IsFolder) 129 | { 130 | //make sure que les enfant ont été loadé 131 | if (!this.ChildLoaded) { this.LoadChild(); } 132 | this.IsOpen = true; 133 | } 134 | } 135 | public void Close() 136 | { 137 | if (this.IsFolder) 138 | { 139 | this.IsOpen = false; 140 | } 141 | } 142 | 143 | //ce constructeur c'est pour quand les propriété sont défini depuis l'extérieur après l'initialisation de l'object 144 | public TreeObject(string sUiName, TreeView4 sTV) 145 | { 146 | this.TV = sTV; 147 | 148 | this.Parent = null; 149 | this.Path = ""; 150 | this.UiName = sUiName; 151 | } 152 | //this s'ajoute automatiquement au parent spécifié 153 | public TreeObject(TreeObject sParent, string sPath, TOType stType, TreeView4 sTV) 154 | { 155 | this.TV = sTV; 156 | 157 | this.Parent = sParent; 158 | if (sParent != null) 159 | { 160 | sParent.listChild.Add(this); 161 | this.SetThisAsNextDirectionOf(sParent.dir); 162 | } 163 | 164 | this.Path = sPath; 165 | this.UiName = System.IO.Path.GetFileName(sPath); 166 | 167 | this.tType = stType; 168 | 169 | } 170 | 171 | 172 | //dessine le nom et les sous objet de this à partir de la coordonné graphique spécifié 173 | public void DrawFullAt(int uix, int uiy, Graphics g) 174 | { 175 | 176 | this.DrawNameAt(uix, uiy, g); 177 | 178 | //si this est un folder et qu'il est ouvert, on dessine la racine 179 | if (this.IsFolder) 180 | { 181 | if (this.IsOpen) 182 | { 183 | //on rajoute le caractère "+" à UiName parce que les dossier ont toujours un caractère de plus qui indique s'ils sont ouvert ou fermé 184 | int namewidth = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width); 185 | //position horizontal à laquelle dessiner la root 186 | int rootx = uix + namewidth; 187 | if (this.dir == Dir.UpLeft || this.dir == Dir.DownLeft) { rootx = uix - namewidth; } 188 | 189 | //fait dessiner la root à l'endroit prévu 190 | this.DrawSubAt(rootx, uiy, g); 191 | 192 | } 193 | } 194 | 195 | 196 | } 197 | 198 | 199 | //basé sur les dernière coordonné graphique enregistré, retourne l'objet situé sous la coordonné graphique spécifié 200 | public TreeObject GetObjetUnderPos(int uix, int uiy) 201 | { 202 | //on commence par checker this 203 | if (this.uiLastX <= uix && uix < this.uiLastX + this.uiLastWidth) 204 | { 205 | if (this.uiLastY <= uiy && uiy < this.uiLastY + (int)(this.TV.uifItemTextHeight)) 206 | { 207 | return this; 208 | } 209 | } 210 | 211 | //si this est un dossier OUVERT, il faut maintenant vérifier les enfant 212 | if (this.IsFolder && this.IsOpen) 213 | { 214 | foreach (TreeObject to in this.listChild) 215 | { 216 | TreeObject torep = to.GetObjetUnderPos(uix, uiy); 217 | if (torep != null) { return torep; } 218 | } 219 | } 220 | 221 | return null; 222 | } 223 | 224 | //coordonné graphique du text en haut à gauche lors du dernier refresh graphique. mis à jour par DrawNameAt(,,) 225 | public int uiLastX = 0; 226 | public int uiLastY = 0; 227 | public int uiLastWidth = 0; 228 | 229 | //dessine le nom de this à partir de la coordonné graphique spécifié 230 | public void DrawNameAt(int uix, int uiy, Graphics g) 231 | { 232 | if (this.IsFolder) 233 | { 234 | //fait la chaine de text qui est le nom 235 | string strName = this.UiName; 236 | 237 | //dessine le nom selon que le nom doit aller à droite ou à gauche 238 | if (this.dir == Dir.DownRight || this.dir == Dir.UpRight) 239 | { 240 | //on ajoute le caractère qui indique si le dossier est ouvert ou fermé 241 | if (this.IsOpen) 242 | { 243 | strName = "-" + strName; 244 | } 245 | else { strName = "+" + strName; } 246 | 247 | //on obtien le rectangle d'arrière plan 248 | int NameWidth = (int)(g.MeasureString(strName, this.TV.uiItemFont).Width); 249 | Rectangle NameRect = new Rectangle(uix, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 250 | //sauvgarde la position 251 | this.uiLastX = NameRect.X; 252 | this.uiLastY = NameRect.Y; 253 | this.uiLastWidth = NameRect.Width; 254 | 255 | //on remplit l'arrière plan 256 | g.FillRectangle(this.TV.uiFolderBackBrush, NameRect); 257 | 258 | //on le dessine à droite 259 | g.DrawString(strName, this.TV.uiItemFont, Brushes.White, (float)uix, (float)uiy - (this.TV.uifItemTextHeight / 2f)); 260 | 261 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 262 | if (this.TV.ActualElement == this) 263 | { 264 | g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 265 | } 266 | 267 | } 268 | if (this.dir == Dir.UpLeft || this.dir == Dir.DownLeft) 269 | { 270 | //on ajoute le caractère qui indique si le dossier est ouvert ou fermé 271 | if (this.IsOpen) 272 | { 273 | strName += "-"; 274 | } 275 | else { strName += "+"; } 276 | 277 | //on obtien le rectangle d'arrière plan 278 | int NameWidth = (int)(g.MeasureString(strName, this.TV.uiItemFont).Width); 279 | Rectangle NameRect = new Rectangle(uix - NameWidth, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 280 | //sauvgarde la position 281 | this.uiLastX = NameRect.X; 282 | this.uiLastY = NameRect.Y; 283 | this.uiLastWidth = NameRect.Width; 284 | 285 | //on remplit l'arrière plan 286 | g.FillRectangle(this.TV.uiFolderBackBrush, NameRect); 287 | 288 | //on le dessine à gauche 289 | g.DrawString(strName, this.TV.uiItemFont, Brushes.White, (float)(uix - NameWidth), (float)uiy - (this.TV.uifItemTextHeight / 2f)); 290 | 291 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 292 | if (this.TV.ActualElement == this) 293 | { 294 | g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 295 | } 296 | 297 | } 298 | 299 | } 300 | else //si this est un fichier 301 | { 302 | //on check dans quel direction il faut dessiner le nom 303 | bool DrawToTheRight = true; 304 | if (this.Parent != null) 305 | { 306 | DrawToTheRight = this.Parent.dir == Dir.DownLeft || this.Parent.dir == Dir.DownRight; 307 | } 308 | 309 | //on dessine le nom 310 | if (DrawToTheRight) 311 | { 312 | //on obtien le rectangle d'arrière plan 313 | int NameWidth = (int)(g.MeasureString(this.UiName, this.TV.uiItemFont).Width); 314 | Rectangle NameRect = new Rectangle(uix, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 315 | //sauvgarde la position 316 | this.uiLastX = NameRect.X; 317 | this.uiLastY = NameRect.Y; 318 | this.uiLastWidth = NameRect.Width; 319 | 320 | g.DrawString(this.UiName, this.TV.uiItemFont, Brushes.White, (float)uix, (float)uiy - (this.TV.uifItemTextHeight / 2f)); 321 | 322 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 323 | if (this.TV.ActualElement == this) 324 | { 325 | g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 326 | } 327 | 328 | } 329 | else 330 | { 331 | //on obtien le rectangle d'arrière plan 332 | int NameWidth = (int)(g.MeasureString(this.UiName, this.TV.uiItemFont).Width); 333 | Rectangle NameRect = new Rectangle(uix - NameWidth, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 334 | //sauvgarde la position 335 | this.uiLastX = NameRect.X; 336 | this.uiLastY = NameRect.Y; 337 | this.uiLastWidth = NameRect.Width; 338 | 339 | g.DrawString(this.UiName, this.TV.uiItemFont, Brushes.White, (float)uix - g.MeasureString(this.UiName, this.TV.uiItemFont).Width, (float)uiy - (this.TV.uifItemTextHeight / 2f)); 340 | 341 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 342 | if (this.TV.ActualElement == this) 343 | { 344 | g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 345 | } 346 | 347 | } 348 | 349 | } 350 | } 351 | 352 | //si this est un dossier, dessine tout les élément enfant, dans la bonne direction, en commencant la racine à l'endroit spécifié 353 | public void DrawSubAt(int uix, int uiy, Graphics g) 354 | { 355 | if (this.IsFolder) 356 | { 357 | Pen penLineDiag = Pens.DimGray; 358 | int uiDiagSpace = this.TV.uiDiagSpace; 359 | 360 | if (this.dir == Dir.DownLeft) 361 | { 362 | //g.DrawLine(Pens.Red, uix, uiy, uix - 20, uiy + 20); 363 | 364 | //s'il n'y a pas d'enfant on fait une ligne vide 365 | if (this.listChild.Count <= 0) 366 | { 367 | g.DrawLine(Pens.Red, uix, uiy, uix - 20, uiy + 20); 368 | } 369 | else 370 | { 371 | int ActualUiX = uix; 372 | int ActualUiY = uiy; 373 | int CurrentMaxWidth = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width); //largeur maximale d'un objet fermé pour ne pas embarquer sur un autre arbre. cette valeur augmente pendant qu'on dessend dans les enfant. 374 | int index = 0; 375 | while (index < this.listChild.Count) 376 | { 377 | //on passe à l'enfant suivant 378 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY + uiDiagSpace); 379 | ActualUiX -= uiDiagSpace; 380 | ActualUiY += uiDiagSpace; 381 | 382 | TreeObject to = this.listChild[index]; 383 | int toNameWidth = 0; //largeur du nom de l'objet 384 | if (to.IsFolder) { toNameWidth = (int)(g.MeasureString(to.UiName + "+", this.TV.uiItemFont).Width); } 385 | else { toNameWidth = (int)(g.MeasureString(to.UiName, this.TV.uiItemFont).Width); } 386 | 387 | //si c'est un dossier ouvert 388 | if (to.IsFolder && to.IsOpen) 389 | { 390 | //ajoute le up height 391 | int toUpHeight = to.GetUpHeight(g); 392 | int Delta = (int)((float)toUpHeight / 1.4142f); 393 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta, ActualUiY + Delta); 394 | ActualUiX -= Delta; 395 | ActualUiY += Delta; 396 | 397 | //dessine une "coche" qui indique plus clairement à l'user que cet enfant apartient à cette ligne 398 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY); 399 | 400 | //maintenant fait dessiner l'élément enfant 401 | to.DrawFullAt(ActualUiX + uiDiagSpace, ActualUiY, g); 402 | 403 | ////ajoute le down height 404 | //int toDownHeight = to.GetDownHeight(g); 405 | //Delta = (int)((float)toDownHeight / 1.4142f); 406 | //g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta, ActualUiY + Delta); 407 | //ActualUiX -= Delta; 408 | //ActualUiY += Delta; 409 | 410 | CurrentMaxWidth = toNameWidth - uiDiagSpace; 411 | 412 | } 413 | else //si c'est un dossier fermé ou un fichier 414 | { 415 | 416 | //check si le nom est trop large 417 | if (toNameWidth > CurrentMaxWidth) 418 | { 419 | //si le nom est trop large on doit lui ajouter sa upheight necessaire pour qu'il ne le touche plus 420 | 421 | //ajoute le up height 422 | //int toUpHeight = to.GetUpHeight(g); 423 | //int Delta = (int)((float)toUpHeight / 1.4142f); 424 | int Delta = toNameWidth - CurrentMaxWidth; 425 | Delta /= 2; 426 | 427 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta, ActualUiY + Delta); 428 | ActualUiX -= Delta; 429 | ActualUiY += Delta; 430 | 431 | //maintenant qu'on a dessendu, la largeur maximal augmente 432 | CurrentMaxWidth += 2 * Delta; 433 | 434 | } 435 | 436 | 437 | 438 | //dessine une "coche" qui indique plus clairement à l'user que cet enfant apartient à cette ligne 439 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY); 440 | 441 | //maintenant fait dessiner l'élément enfant 442 | to.DrawFullAt(ActualUiX + uiDiagSpace, ActualUiY, g); 443 | 444 | 445 | } 446 | 447 | //next iteration 448 | index++; 449 | CurrentMaxWidth += 2 * uiDiagSpace; 450 | } 451 | 452 | } 453 | } 454 | else if (this.dir == Dir.DownRight) 455 | { 456 | //g.DrawLine(Pens.Red, uix, uiy, uix + 20, uiy + 20); 457 | 458 | //s'il n'y a pas d'enfant on fait une ligne vide 459 | if (this.listChild.Count <= 0) 460 | { 461 | g.DrawLine(Pens.Red, uix, uiy, uix + 20, uiy + 20); 462 | } 463 | else 464 | { 465 | int ActualUiX = uix; 466 | int ActualUiY = uiy; 467 | int index = 0; 468 | TreeObject LastTO = null; 469 | int LeftDownHeight = 0; //down height restant des élément supérieur 470 | while (index < this.listChild.Count) 471 | { 472 | //on passe à l'enfant suivant 473 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY + uiDiagSpace); 474 | ActualUiX += uiDiagSpace; 475 | ActualUiY += uiDiagSpace; 476 | 477 | LeftDownHeight -= (int)((float)uiDiagSpace * 1.4142f); // * 478 | if (LeftDownHeight < 0) { LeftDownHeight = 0; } 479 | 480 | 481 | TreeObject to = this.listChild[index]; 482 | 483 | //si c'est un dossier et qu'il est ouvert 484 | if (to.IsFolder && to.IsOpen) 485 | { 486 | //ajoute le down height du truc qui précède le dossier, s'il y en a un 487 | //if (LastTO != null) 488 | //{ 489 | // //ajoute le down height 490 | // int lasttoDownHeight = LastTO.GetDownHeight(g); 491 | // int Delta2 = (int)((float)lasttoDownHeight / 1.4142f); 492 | // g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta2, ActualUiY + Delta2); 493 | // ActualUiX += Delta2; 494 | // ActualUiY += Delta2; 495 | //} 496 | 497 | int Delta2 = (int)((float)LeftDownHeight / 1.4142f); 498 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta2, ActualUiY + Delta2); 499 | ActualUiX += Delta2; 500 | ActualUiY += Delta2; 501 | LeftDownHeight = 0; //on le met à 0 pour être sûr que sa futur valeur sera 502 | 503 | 504 | //ajoute le up height 505 | int toUpHeight = to.GetUpHeight(g); 506 | int Delta = (int)((float)toUpHeight / 1.4142f); 507 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta, ActualUiY + Delta); 508 | ActualUiX += Delta; 509 | ActualUiY += Delta; 510 | } 511 | 512 | 513 | //dessine une "coche" qui indique plus clairement à l'user que cet enfant apartient à cette ligne 514 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY); 515 | 516 | //maintenant fait dessiner l'élément enfant 517 | to.DrawFullAt(ActualUiX + uiDiagSpace, ActualUiY, g); 518 | 519 | //ajoute le down height 520 | int toDownHeight = to.GetDownHeight(g) + (int)((float)uiDiagSpace * 1.4142f * 2f); 521 | if (toDownHeight > LeftDownHeight) { LeftDownHeight = toDownHeight; } 522 | else { } 523 | 524 | 525 | //Delta = (int)((float)toDownHeight / 1.4142f); 526 | //g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta, ActualUiY + Delta); 527 | //ActualUiX += Delta; 528 | //ActualUiY += Delta; 529 | 530 | //next iteration 531 | index++; 532 | LastTO = to; 533 | } 534 | 535 | } 536 | } 537 | else if (this.dir == Dir.UpRight) 538 | { 539 | //g.DrawLine(Pens.Red, uix, uiy, uix + 20, uiy - 20); 540 | 541 | //s'il n'y a pas d'enfant on fait une ligne vide 542 | if (this.listChild.Count <= 0) 543 | { 544 | g.DrawLine(Pens.Red, uix, uiy, uix + 20, uiy - 20); 545 | } 546 | else 547 | { 548 | int ActualUiX = uix; 549 | int ActualUiY = uiy; 550 | int CurrentMaxWidth = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width); //largeur maximale d'un objet fermé pour ne pas embarquer sur un autre arbre. cette valeur augmente pendant qu'on dessend dans les enfant. 551 | int index = 0; 552 | while (index < this.listChild.Count) 553 | { 554 | //on passe à l'enfant suivant 555 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY - uiDiagSpace); 556 | ActualUiX += uiDiagSpace; 557 | ActualUiY -= uiDiagSpace; 558 | 559 | TreeObject to = this.listChild[index]; 560 | int toNameWidth = 0; //largeur du nom de l'objet 561 | if (to.IsFolder) { toNameWidth = (int)(g.MeasureString(to.UiName + "+", this.TV.uiItemFont).Width); } 562 | else { toNameWidth = (int)(g.MeasureString(to.UiName, this.TV.uiItemFont).Width); } 563 | 564 | //si c'est un dossier ouvert 565 | if (to.IsFolder && to.IsOpen) 566 | { 567 | //ajoute le up height 568 | int toUpHeight = to.GetUpHeight(g); 569 | int Delta = (int)((float)toUpHeight / 1.4142f); 570 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta, ActualUiY - Delta); 571 | ActualUiX += Delta; 572 | ActualUiY -= Delta; 573 | 574 | //dessine une "coche" qui indique plus clairement à l'user que cet enfant apartient à cette ligne 575 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY); 576 | 577 | //maintenant fait dessiner l'élément enfant 578 | to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY, g); 579 | 580 | 581 | //la largeur maximal des objet fermé à venir doit être reseté à partir d'ici 582 | CurrentMaxWidth = toNameWidth - uiDiagSpace; 583 | } 584 | else //c'est un dossier fermé ou un fichier 585 | { 586 | 587 | //check si le nom est trop large 588 | if (toNameWidth > CurrentMaxWidth) 589 | { 590 | //si le nom est trop large on doit lui ajouter sa upheight necessaire pour qu'il ne le touche plus 591 | 592 | //ajoute le up height 593 | //int toUpHeight = to.GetUpHeight(g); 594 | //int Delta = (int)((float)toUpHeight / 1.4142f); 595 | int Delta = toNameWidth - CurrentMaxWidth; 596 | Delta /= 2; 597 | 598 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta, ActualUiY - Delta); 599 | ActualUiX += Delta; 600 | ActualUiY -= Delta; 601 | 602 | //maintenant qu'on a dessendu, la largeur maximal augmente 603 | CurrentMaxWidth += 2 * Delta; 604 | 605 | } 606 | 607 | 608 | 609 | //dessine une "coche" qui indique plus clairement à l'user que cet enfant apartient à cette ligne 610 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY); 611 | 612 | //maintenant fait dessiner l'élément enfant 613 | to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY, g); 614 | 615 | 616 | } 617 | 618 | ////ajoute le up height 619 | //int toUpHeight = to.GetUpHeight(g); 620 | //int Delta = (int)((float)toUpHeight / 1.4142f); 621 | //g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta, ActualUiY - Delta); 622 | //ActualUiX += Delta; 623 | //ActualUiY -= Delta; 624 | 625 | ////dessine une "coche" qui indique plus clairement à l'user que cet enfant apartient à cette ligne 626 | //g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY); 627 | 628 | ////maintenant fait dessiner l'élément enfant 629 | //to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY, g); 630 | 631 | ////ajoute le down height 632 | //int toDownHeight = to.GetDownHeight(g); 633 | //Delta = (int)((float)toDownHeight / 1.4142f); 634 | //g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + Delta, ActualUiY - Delta); 635 | //ActualUiX += Delta; 636 | //ActualUiY -= Delta; 637 | 638 | //next iteration 639 | index++; 640 | CurrentMaxWidth += 2 * uiDiagSpace; 641 | } 642 | 643 | } 644 | } 645 | else if (this.dir == Dir.UpLeft) 646 | { 647 | //g.DrawLine(Pens.Red, uix, uiy, uix - 20, uiy - 20); 648 | 649 | //s'il n'y a pas d'enfant on fait une ligne vide 650 | if (this.listChild.Count <= 0) 651 | { 652 | g.DrawLine(Pens.Red, uix, uiy, uix - 20, uiy - 20); 653 | } 654 | else 655 | { 656 | int ActualUiX = uix; 657 | int ActualUiY = uiy; 658 | int index = 0; 659 | TreeObject LastTO = null; 660 | int LeftDownHeight = 0; //down height restant des élément supérieur 661 | while (index < this.listChild.Count) 662 | { 663 | //on passe à l'enfant suivant 664 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY - uiDiagSpace); 665 | ActualUiX -= uiDiagSpace; 666 | ActualUiY -= uiDiagSpace; 667 | 668 | LeftDownHeight -= (int)((float)uiDiagSpace * 1.4142f); 669 | if (LeftDownHeight < 0) { LeftDownHeight = 0; } 670 | 671 | 672 | TreeObject to = this.listChild[index]; 673 | 674 | //si c'est un dossier et qu'il est ouvert 675 | if (to.IsFolder && to.IsOpen) 676 | { 677 | //ajoute le down height du truc qui précède le dossier, s'il y en a un 678 | //if (LastTO != null) 679 | //{ 680 | // //ajoute le down height 681 | // int lasttoDownHeight = LastTO.GetDownHeight(g); 682 | // int Delta2 = (int)((float)lasttoDownHeight / 1.4142f); 683 | // g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta2, ActualUiY - Delta2); 684 | // ActualUiX -= Delta2; 685 | // ActualUiY -= Delta2; 686 | //} 687 | int Delta2 = (int)((float)LeftDownHeight / 1.4142f); 688 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta2, ActualUiY - Delta2); 689 | ActualUiX -= Delta2; 690 | ActualUiY -= Delta2; 691 | LeftDownHeight = 0; //on le met à 0 pour être sûr que sa futur valeur sera 692 | 693 | 694 | //ajoute le up height 695 | int toUpHeight = to.GetUpHeight(g); 696 | int Delta = (int)((float)toUpHeight / 1.4142f); 697 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta, ActualUiY - Delta); 698 | ActualUiX -= Delta; 699 | ActualUiY -= Delta; 700 | } 701 | 702 | 703 | ////ajoute le up height 704 | //int toUpHeight = to.GetUpHeight(g); 705 | //int Delta = (int)((float)toUpHeight / 1.4142f); 706 | //g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta, ActualUiY - Delta); 707 | //ActualUiX -= Delta; 708 | //ActualUiY -= Delta; 709 | 710 | //dessine une "coche" qui indique plus clairement à l'user que cet enfant apartient à cette ligne 711 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY); 712 | 713 | //maintenant fait dessiner l'élément enfant 714 | to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY, g); 715 | 716 | //ajoute le down height 717 | int toDownHeight = to.GetDownHeight(g) + (int)((float)uiDiagSpace * 1.4142f * 2f); 718 | if (toDownHeight > LeftDownHeight) { LeftDownHeight = toDownHeight; } 719 | else { } 720 | 721 | 722 | //int toDownHeight = to.GetDownHeight(g); 723 | //Delta = (int)((float)toDownHeight / 1.4142f); 724 | //g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - Delta, ActualUiY - Delta); 725 | //ActualUiX -= Delta; 726 | //ActualUiY -= Delta; 727 | 728 | //next iteration 729 | index++; 730 | LastTO = to; 731 | } 732 | 733 | } 734 | } 735 | } 736 | } 737 | 738 | 739 | 740 | //retourne le width relatif au parent 741 | public int GetWidth(Graphics g) 742 | { 743 | if (this.IsFolder) 744 | { 745 | if (!this.IsOpen) 746 | { 747 | //le dossier est fermé 748 | return (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 749 | 750 | } 751 | else //le dossier est ouvert 752 | { 753 | //puisque le dossier est ouvert, il faut prendre en considération tout les contenu pour toute les direction 754 | if (this.dir == Dir.DownLeft || this.dir == Dir.UpRight) 755 | { 756 | //le width c'est le width du nom du dossier + toute les hauteur relative des truc à l'intérieur 757 | int rep = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 758 | rep += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 759 | 760 | int CurrentMaxWidth = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width); 761 | 762 | foreach (TreeObject to in this.listChild) 763 | { 764 | int toNameWidth = 0; //largeur du nom de l'objet 765 | if (to.IsFolder) { toNameWidth = (int)(g.MeasureString(to.UiName + "+", this.TV.uiItemFont).Width); } 766 | else { toNameWidth = (int)(g.MeasureString(to.UiName, this.TV.uiItemFont).Width); } 767 | 768 | rep += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 769 | 770 | //si c'est un dossier ouvert 771 | if (to.IsFolder && to.IsOpen) 772 | { 773 | rep += to.GetUpHeight(g); 774 | CurrentMaxWidth = toNameWidth - this.TV.uiDiagSpace; 775 | } 776 | else //si c'est un dossier fermé ou un fichier 777 | { 778 | //on doit analyser si le nom de l'élément est trop grand ce qui fait baisser l'élément 779 | if (toNameWidth > CurrentMaxWidth) 780 | { 781 | int delta = (toNameWidth - CurrentMaxWidth) / 2; 782 | rep += (int)((float)delta * 1.4142f); 783 | CurrentMaxWidth += 2 * delta; 784 | } 785 | 786 | } 787 | 788 | //next iteration 789 | CurrentMaxWidth += 2 * this.TV.uiDiagSpace; 790 | } 791 | return rep; 792 | 793 | } 794 | else if (this.dir == Dir.DownRight || this.dir == Dir.UpLeft) 795 | { 796 | 797 | int rep = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 798 | rep += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 799 | 800 | int MinWidth = 0; //largeur minimal nécessaire pour tout contenir. 801 | 802 | TreeObject LastTO = null; 803 | int LastTODownHeight = 0; 804 | int LeftDownHeight = 0; //down height restant des élément supérieur 805 | foreach (TreeObject to in this.listChild) 806 | { 807 | rep += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 808 | 809 | LeftDownHeight -= (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 810 | if (LeftDownHeight < 0) { LeftDownHeight = 0; } 811 | 812 | 813 | //si c'est un dossier ouvert 814 | if (to.IsFolder && to.IsOpen) 815 | { 816 | //if (LastTO != null) 817 | //{ 818 | // rep += LastTODownHeight; 819 | //} 820 | rep += LeftDownHeight; 821 | LeftDownHeight = 0; 822 | 823 | 824 | rep += to.GetUpHeight(g); 825 | } 826 | else //c'est un dossier fermé ou un fichier 827 | { 828 | 829 | } 830 | 831 | 832 | //ajoute le down height 833 | int toDownHeight = to.GetDownHeight(g) + (int)((float)(this.TV.uiDiagSpace) * 1.4142f * 2f); 834 | if (toDownHeight > LeftDownHeight) { LeftDownHeight = toDownHeight; } 835 | else { } 836 | 837 | 838 | //calcul le down height 839 | int toDownHeight2 = to.GetDownHeight(g); 840 | 841 | //on check si la width minimum requis pour contenir cet élément est plus grand que celui déjà enregistré 842 | int ActualMinWidth = rep + toDownHeight2; 843 | if (ActualMinWidth > MinWidth) { MinWidth = ActualMinWidth; } 844 | 845 | //next iteration 846 | LastTO = to; 847 | LastTODownHeight = toDownHeight2; 848 | } 849 | 850 | 851 | return MinWidth; 852 | } 853 | //else if (this.dir == Dir.UpRight) 854 | //{ 855 | 856 | //} 857 | //else if (this.dir == Dir.UpLeft) 858 | //{ 859 | // int rep = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 860 | // rep += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 861 | 862 | // int MinWidth = 0; //largeur minimal nécessaire pour tout contenir. 863 | 864 | // TreeObject LastTO = null; 865 | // int LastTODownHeight = 0; 866 | // foreach (TreeObject to in this.listChild) 867 | // { 868 | // rep += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 869 | 870 | // //si c'est un dossier ouvert 871 | // if (to.IsFolder && to.IsOpen) 872 | // { 873 | // if (LastTO != null) 874 | // { 875 | // rep += LastTODownHeight; 876 | // } 877 | // rep += to.GetUpHeight(g); 878 | // } 879 | // else //c'est un dossier fermé ou un fichier 880 | // { 881 | 882 | // } 883 | 884 | // //calcul le down height 885 | // int toDownHeight = to.GetDownHeight(g); 886 | 887 | // //on check si la width minimum requis pour contenir cet élément est plus grand que celui déjà enregistré 888 | // int ActualMinWidth = rep + toDownHeight + (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 889 | // if (ActualMinWidth > MinWidth) { MinWidth = ActualMinWidth; } 890 | 891 | // //next iteration 892 | // LastTO = to; 893 | // LastTODownHeight = toDownHeight; 894 | // } 895 | 896 | 897 | // return MinWidth; 898 | //} 899 | 900 | 901 | 902 | //le width c'est le width du nom du dossier + toute les hauteur relative des truc à l'intérieur 903 | int rep2 = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 904 | rep2 += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 905 | 906 | foreach (TreeObject to in this.listChild) 907 | { 908 | rep2 += to.GetUpHeight(g); 909 | rep2 += to.GetDownHeight(g); 910 | rep2 += (int)((float)(this.TV.uiDiagSpace) * 1.4142f); 911 | } 912 | return rep2; 913 | 914 | } 915 | } 916 | else //alors this est un fichier 917 | { 918 | return (int)(g.MeasureString(this.UiName, this.TV.uiItemFont).Width / 1.4142f); 919 | } 920 | } 921 | 922 | //retourne le up height relatif au parent 923 | public int GetUpHeight(Graphics g) 924 | { 925 | if (this.Parent != null) 926 | { 927 | if (this.IsFolder) //dossier 928 | { 929 | if (this.IsOpen) //dossier ouvert 930 | { 931 | if (this.Parent.dir == Dir.DownLeft) 932 | { 933 | //on cherche le plus gros width à l'intérieur et on l'additionne à la hauteur du nom du dossier 934 | int BiggestWidth = 0; 935 | foreach (TreeObject to in this.listChild) 936 | { 937 | int towidth = to.GetWidth(g); 938 | if (towidth > BiggestWidth) { BiggestWidth = towidth; } 939 | } 940 | return (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f) + BiggestWidth; 941 | } 942 | else if (this.Parent.dir == Dir.DownRight) 943 | { 944 | //on cherche le plus gros width à l'intérieur et on lui soustrait la hauteur du nom du dossier 945 | int BiggestWidth = 0; 946 | foreach (TreeObject to in this.listChild) 947 | { 948 | int towidth = to.GetWidth(g); 949 | if (towidth > BiggestWidth) { BiggestWidth = towidth; } 950 | } 951 | int rep = BiggestWidth - (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 952 | //on make sure que la up height n'est pas négative 953 | if (rep < 0) { rep = 0; } 954 | return rep; 955 | } 956 | else if (this.Parent.dir == Dir.UpRight) 957 | { 958 | //on cherche le plus gros width à l'intérieur 959 | int BiggestWidth = 0; 960 | foreach (TreeObject to in this.listChild) 961 | { 962 | int towidth = to.GetWidth(g); 963 | if (towidth > BiggestWidth) { BiggestWidth = towidth; } 964 | } 965 | return (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f) + BiggestWidth; 966 | } 967 | else if (this.Parent.dir == Dir.UpLeft) 968 | { 969 | //on cherche le plus gros width à l'intérieur et on lui soustrait la hauteur du nom du dossier 970 | int BiggestWidth = 0; 971 | foreach (TreeObject to in this.listChild) 972 | { 973 | int towidth = to.GetWidth(g); 974 | if (towidth > BiggestWidth) { BiggestWidth = towidth; } 975 | } 976 | int rep = BiggestWidth - (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 977 | //on make sure que la up height n'est pas négative 978 | if (rep < 0) { rep = 0; } 979 | return rep; 980 | } 981 | } 982 | else //dossier fermé 983 | { 984 | if (this.Parent.dir == Dir.DownLeft) 985 | { 986 | return (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 987 | } 988 | else if (this.Parent.dir == Dir.DownRight) 989 | { 990 | return 0; 991 | } 992 | else if (this.Parent.dir == Dir.UpRight) 993 | { 994 | return (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 995 | } 996 | else if (this.Parent.dir == Dir.UpLeft) 997 | { 998 | return 0; 999 | } 1000 | } 1001 | } 1002 | else //fichier 1003 | { 1004 | if (this.Parent.dir == Dir.DownLeft) 1005 | { 1006 | return (int)(g.MeasureString(this.UiName, this.TV.uiItemFont).Width / 1.4142f); 1007 | } 1008 | else if (this.Parent.dir == Dir.DownRight) 1009 | { 1010 | return 0; 1011 | } 1012 | else if (this.Parent.dir == Dir.UpRight) 1013 | { 1014 | return (int)(g.MeasureString(this.UiName, this.TV.uiItemFont).Width / 1.4142f); 1015 | } 1016 | else if (this.Parent.dir == Dir.UpLeft) 1017 | { 1018 | return 0; 1019 | } 1020 | } 1021 | } 1022 | else { return 0; } 1023 | 1024 | return 0; 1025 | } 1026 | 1027 | //retourne le down height relatif au parent 1028 | public int GetDownHeight(Graphics g) 1029 | { 1030 | if (this.Parent != null) 1031 | { 1032 | if (this.IsFolder) //dossier 1033 | { 1034 | if (this.Parent.dir == Dir.DownLeft) 1035 | { 1036 | return 0; 1037 | } 1038 | else if (this.Parent.dir == Dir.DownRight) 1039 | { 1040 | return (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 1041 | } 1042 | else if (this.Parent.dir == Dir.UpRight) 1043 | { 1044 | return 0; 1045 | } 1046 | else if (this.Parent.dir == Dir.UpLeft) 1047 | { 1048 | return (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width / 1.4142f); 1049 | } 1050 | } 1051 | else //fichier 1052 | { 1053 | if (this.Parent.dir == Dir.DownLeft) 1054 | { 1055 | return 0; 1056 | } 1057 | else if (this.Parent.dir == Dir.DownRight) 1058 | { 1059 | return (int)(g.MeasureString(this.UiName, this.TV.uiItemFont).Width / 1.4142f); 1060 | } 1061 | else if (this.Parent.dir == Dir.UpRight) 1062 | { 1063 | return 0; 1064 | } 1065 | else if (this.Parent.dir == Dir.UpLeft) 1066 | { 1067 | return (int)(g.MeasureString(this.UiName, this.TV.uiItemFont).Width / 1.4142f); 1068 | } 1069 | } 1070 | } 1071 | 1072 | return 0; 1073 | } 1074 | 1075 | 1076 | 1077 | } 1078 | 1079 | 1080 | 1081 | private void BuildTreeRoot() 1082 | { 1083 | TreeObject r = new TreeObject("This PC", this); 1084 | r.tType = TOType.folder; 1085 | r.dir = Dir.DownLeft; // DownLeft 1086 | 1087 | //TreeObject toC = new TreeObject(r, "C:\\", TOType.folder, this); 1088 | //toC.UiName = "C:\\"; 1089 | //TreeObject toD = new TreeObject(r, "D:\\", TOType.folder, this); 1090 | //toD.UiName = "D:\\"; 1091 | 1092 | string[] arDrives = System.IO.Directory.GetLogicalDrives(); 1093 | foreach (string l in arDrives) 1094 | { 1095 | TreeObject to = new TreeObject(r, l, TOType.folder, this); 1096 | to.UiName = l; 1097 | } 1098 | 1099 | 1100 | 1101 | r.ChildLoaded = true; 1102 | 1103 | this.Root = r; 1104 | this.ActualElement = r; 1105 | } 1106 | 1107 | #endregion 1108 | #region navigation 1109 | 1110 | //à caller depuis l'extérieur, par celui qui recoit les touche 1111 | public void KeyDown(KeyEventArgs e) 1112 | { 1113 | 1114 | //navigation verticale dans le dossier 1115 | if (e.KeyCode == Keys.Up) 1116 | { 1117 | ////on remonte dans le dossier 1118 | //on check si l'objet actuel a un parent 1119 | if (this.ActualElement.Parent != null) 1120 | { 1121 | //nous recherchons l'index de l'élément actuel 1122 | int ActualIndex = this.ActualElement.Parent.listChild.IndexOf(this.ActualElement); 1123 | //on remonte de 1 1124 | ActualIndex--; 1125 | //si le nouveau index est négatif, alors on remonte au parent 1126 | if (ActualIndex < 0) 1127 | { 1128 | this.ActualElement = this.ActualElement.Parent; 1129 | } 1130 | else 1131 | { 1132 | //si le nouveau index n'est pas négatif, il change pour l'item du nouveau index 1133 | this.ActualElement = this.ActualElement.Parent.listChild[ActualIndex]; 1134 | } 1135 | 1136 | } 1137 | else 1138 | { 1139 | //si l'objet actuel n'a pas de parent, alors nous somme à la racine et il n'y a rien à faire 1140 | 1141 | } 1142 | 1143 | } 1144 | if (e.KeyCode == Keys.Down) 1145 | { 1146 | ////on dessant dans le dossier 1147 | //on check si l'objet actuel a un parent 1148 | if (this.ActualElement.Parent != null) 1149 | { 1150 | //on obtien l'index de l'élément actuel 1151 | int ActualIndex = this.ActualElement.Parent.listChild.IndexOf(this.ActualElement); 1152 | //on augmente l'index pour passer à l'élément suivant 1153 | ActualIndex++; 1154 | //on make sure que l'index existe 1155 | if (ActualIndex < this.ActualElement.Parent.listChild.Count) 1156 | { 1157 | //on set l'élément suivant comme le nouveau élément actuel 1158 | this.ActualElement = this.ActualElement.Parent.listChild[ActualIndex]; 1159 | } 1160 | 1161 | } 1162 | else 1163 | { 1164 | ////si l'object actuel n'a pas de parent, ca veut dire qu'on est à la racine. on change donc pour le premier enfant 1165 | //make sure que la racine (considéré comme un dossier) est ouverte 1166 | if (!this.ActualElement.IsOpen) { this.ActualElement.Open(); } 1167 | //on set l'élément actuel en tant que le premier élément enfant 1168 | //met on check d'abord s'il y a des enfant à l'intérieur du dossier 1169 | if (this.ActualElement.listChild.Count > 0) 1170 | { 1171 | this.ActualElement = this.ActualElement.listChild[0]; 1172 | } 1173 | } 1174 | 1175 | } 1176 | 1177 | //touche enter qui fait ouvrire ou fermer un dossier 1178 | if (e.KeyCode == Keys.Return) 1179 | { 1180 | //check si c'est un dossier 1181 | if (this.ActualElement.IsFolder) 1182 | { 1183 | //si c'est fermé on l'ouvre ou l'inverse 1184 | if (this.ActualElement.IsOpen) 1185 | { 1186 | this.ActualElement.Close(); 1187 | } 1188 | else 1189 | { 1190 | this.ActualElement.Open(); 1191 | } 1192 | } 1193 | } 1194 | 1195 | //touche gauche droite qui fait sortir ou entrer dans un dossier 1196 | if (e.KeyCode == Keys.Right) 1197 | { 1198 | //si c'est un dossier, on entre à l'intérieur 1199 | if (this.ActualElement.IsFolder) 1200 | { 1201 | //make sure que le dossier est ouvert 1202 | if (!this.ActualElement.IsOpen) 1203 | { 1204 | this.ActualElement.Open(); 1205 | } 1206 | //si le dossier a des enfant, on défini l'élément actuel comme son premier enfant 1207 | if (this.ActualElement.listChild.Count > 0) 1208 | { 1209 | this.ActualElement = this.ActualElement.listChild[0]; 1210 | } 1211 | 1212 | } 1213 | } 1214 | if (e.KeyCode == Keys.Left) 1215 | { 1216 | //on remonte au parent, s'il y en a un 1217 | if (this.ActualElement.Parent != null) 1218 | { 1219 | this.ActualElement = this.ActualElement.Parent; 1220 | } 1221 | } 1222 | 1223 | //reset la position actuel 1224 | if (e.KeyCode == Keys.Space) 1225 | { 1226 | this.RootDrawPos = new Point(0, 0); 1227 | } 1228 | 1229 | this.RefreshImage(); 1230 | } 1231 | 1232 | 1233 | 1234 | private void ImageBox_MouseDown(object sender, MouseEventArgs e) 1235 | { 1236 | if (e.Button == MouseButtons.Left) 1237 | { 1238 | //on récupère l'object qui se trouve sous la souris 1239 | TreeObject to = this.GetObjectUnderMouse(); 1240 | if (to != null) 1241 | { 1242 | //on défini cet élément comme l'élément "actuel" ou ayant le focus 1243 | this.ActualElement = to; 1244 | 1245 | //si c'est un fichier, on fait rien. si c'est un dossier, on l'ouvre ou on le ferme 1246 | if (to.IsFolder) 1247 | { 1248 | //on conserve sa position graphique 1249 | int toLastX = to.uiLastX; 1250 | int toLastY = to.uiLastY; 1251 | 1252 | //on ouvre ou on ferme le dossier 1253 | if (to.IsOpen) { to.Close(); } 1254 | else { to.Open(); } 1255 | 1256 | //maintenant on leur fait refresher leur coordonné graphique 1257 | int imgWidth = this.Width; 1258 | int imgHeight = this.Height; 1259 | if (imgWidth < 50) { imgWidth = 50; } 1260 | if (imgHeight < 50) { imgHeight = 50; } 1261 | Point ppMiddle = new Point(imgWidth / 2, imgHeight / 2); 1262 | Bitmap asdfimg = new Bitmap(10, 10); 1263 | Graphics g = Graphics.FromImage(asdfimg); 1264 | this.Root.DrawFullAt(ppMiddle.X + this.RootDrawPos.X, ppMiddle.Y + this.RootDrawPos.Y, g); 1265 | g.Dispose(); 1266 | asdfimg.Dispose(); 1267 | 1268 | 1269 | 1270 | //on réajuste la position graphique pour le le dossier revient au même endroit où il était 1271 | this.RootDrawPos.X -= to.uiLastX - toLastX; 1272 | this.RootDrawPos.Y -= to.uiLastY - toLastY; 1273 | 1274 | 1275 | } 1276 | this.RefreshImage(); 1277 | } 1278 | else //si la fonction a retourné null, alors il n'y a pas d'objet sous la souris et il faut plutot commencer un scroll 1279 | { 1280 | this.StartScroll(); 1281 | 1282 | } 1283 | } 1284 | } 1285 | private void ImageBox_MouseUp(object sender, MouseEventArgs e) 1286 | { 1287 | if (e.Button == MouseButtons.Left) 1288 | { 1289 | //si l'user est en train de scroller on arrête le scroll 1290 | if (this.IsScrolling) 1291 | { 1292 | this.StopScroll(); 1293 | } 1294 | 1295 | 1296 | } 1297 | } 1298 | private void ImageBox_MouseMove(object sender, MouseEventArgs e) 1299 | { 1300 | //if (this.IsScrolling) 1301 | //{ 1302 | // this.ReshowNetralScroll(); 1303 | //} 1304 | } 1305 | 1306 | 1307 | //retourne le TreeObject graphiquement actuellement sous la souris. retourne null s'il n'y en a aucun 1308 | private TreeObject GetObjectUnderMouse() 1309 | { 1310 | return this.Root.GetObjetUnderPos(this.MousePos.X, this.MousePos.Y); 1311 | } 1312 | 1313 | 1314 | 1315 | 1316 | private Timer ScrollTimer = null; 1317 | private void CreateScroll() 1318 | { 1319 | this.ScrollTimer = new Timer(); 1320 | this.ScrollTimer.Interval = 100; // 250 1321 | this.ScrollTimer.Tick += new EventHandler(this.ScrollTimer_Tick); 1322 | 1323 | } 1324 | private void ScrollTimer_Tick(object sender, EventArgs e) 1325 | { 1326 | Point mpos = this.MousePos; 1327 | //calcul le déplacement de la souris 1328 | int dx = mpos.X - this.ScrollStartPos.X; 1329 | int dy = mpos.Y - this.ScrollStartPos.Y; 1330 | 1331 | //applique le déplacement 1332 | this.RootDrawPos.X -= dx; 1333 | this.RootDrawPos.Y -= dy; 1334 | this.RefreshImage(); 1335 | 1336 | //redessine la position neutre du scrolling 1337 | this.ReshowNetralScroll(); 1338 | 1339 | } 1340 | 1341 | private bool IsScrolling = false; //indique si l'user est en train de scroller 1342 | private Point ScrollStartPos = new Point(0, 0); //point de départ du scrolling 1343 | private void StartScroll() 1344 | { 1345 | this.IsScrolling = true; 1346 | this.ScrollStartPos = this.MousePos; 1347 | this.ScrollTimer.Start(); 1348 | } 1349 | private void StopScroll() 1350 | { 1351 | this.IsScrolling = false; 1352 | this.ScrollTimer.Stop(); 1353 | this.ImageBox.Refresh(); 1354 | } 1355 | 1356 | 1357 | //réaffiche graphiquement la position neutre du scrolling avec la flèche qui pointe vers la souris 1358 | private void ReshowNetralScroll() 1359 | { 1360 | this.ImageBox.Refresh(); 1361 | ////redessine la position neutre du scrolling 1362 | Graphics g = this.ImageBox.CreateGraphics(); 1363 | //g.FillRectangle(Brushes.White, this.ScrollStartPos.X - 3, this.ScrollStartPos.Y - 3, 6, 6); 1364 | 1365 | Point mpos = this.MousePos; 1366 | //obtien le décalage de la position de la souris relativement à la position neutre du scrolling 1367 | int dx = mpos.X - this.ScrollStartPos.X; 1368 | int dy = mpos.Y - this.ScrollStartPos.Y; 1369 | 1370 | //obtien l'angle de la souris 1371 | double radAngle = 0d; 1372 | if (dx != 0d || dy != 0d) 1373 | { 1374 | radAngle = Math.Atan2((double)dy, (double)dx); 1375 | } 1376 | 1377 | //converti en degré 1378 | double degAngle = radAngle / Math.PI * 180d; 1379 | 1380 | //obtien et dessine l'image de la flèche 1381 | Bitmap img = this.GetArrowAtAngle(degAngle); 1382 | g.DrawImage(img, this.ScrollStartPos.X - (img.Width / 2), this.ScrollStartPos.Y - (img.Height / 2)); 1383 | img.Dispose(); 1384 | 1385 | g.Dispose(); 1386 | } 1387 | 1388 | 1389 | 1390 | 1391 | private Bitmap imgArrow; 1392 | private void CreateArrow() 1393 | { 1394 | int imgWidth = 50; // 30 1395 | Bitmap img = new Bitmap(imgWidth, imgWidth); 1396 | Graphics g = Graphics.FromImage(img); 1397 | //g.Clear(Color.DimGray); 1398 | g.Clear(Color.Transparent); 1399 | 1400 | 1401 | int w = imgWidth; 1402 | int wd2 = imgWidth / 2; 1403 | int wd4 = (int)((double)(imgWidth) / 3.5d); 1404 | int wd6 = imgWidth / 6; 1405 | int wd8 = imgWidth / 8; 1406 | 1407 | Point[] plArrow = new Point[] { new Point(w - 1, wd2), new Point(wd2 - 1, 0), new Point(wd2 - 1, wd4), new Point(0, wd4), new Point(0, w - wd4), new Point(wd2 - 1, w - wd4), new Point(wd2 - 1, w) }; 1408 | 1409 | Point[] plArrowUp = new Point[] { new Point(w - 1, wd2), new Point(wd2 - 1, 0), new Point(wd2 - 1, wd4), new Point(0, wd4), new Point(0, wd2) }; 1410 | Point[] plArrowDown = new Point[] { new Point(w - 1, wd2), new Point(0, wd2), new Point(0, w - wd4), new Point(wd2 - 1, w - wd4), new Point(wd2 - 1, w) }; 1411 | Point[] plArrowMiddle = new Point[] { new Point(w - 1, wd2), new Point(wd2 + 1, wd6), new Point(wd2 + 1, wd4 + wd8), new Point(0, wd4 + wd8), new Point(0, w - wd4 - wd8), new Point(wd2 + 1, w - wd4 - wd8), new Point(wd2 + 1, w - wd6) }; 1412 | 1413 | g.FillPolygon(Brushes.SteelBlue, plArrowUp); 1414 | g.FillPolygon(Brushes.CornflowerBlue, plArrowDown); 1415 | g.FillPolygon(Brushes.DodgerBlue, plArrowMiddle); 1416 | g.DrawPolygon(Pens.White, plArrow); 1417 | 1418 | g.Dispose(); 1419 | this.imgArrow = img; 1420 | } 1421 | 1422 | 1423 | private Bitmap GetArrowAtAngle(double angle) 1424 | { 1425 | int imgWidth = (int)((double)(this.imgArrow.Width) * 1.4142d); 1426 | Bitmap img = new Bitmap(imgWidth, imgWidth); 1427 | Graphics g = Graphics.FromImage(img); 1428 | g.Clear(Color.Transparent); 1429 | 1430 | g.TranslateTransform((float)imgWidth / 2f, (float)imgWidth / 2f); 1431 | g.RotateTransform((float)angle); 1432 | g.TranslateTransform((float)imgWidth / -2f, (float)imgWidth / -2f); 1433 | 1434 | int delta = (imgWidth / 2) - (this.imgArrow.Width / 2); 1435 | g.DrawImage(this.imgArrow, delta, delta); 1436 | 1437 | 1438 | g.Dispose(); 1439 | return img; 1440 | } 1441 | 1442 | 1443 | 1444 | #endregion 1445 | 1446 | 1447 | public TreeView4() 1448 | { 1449 | this.ImageBox = new PictureBox(); 1450 | this.ImageBox.BackColor = Color.Black; 1451 | this.ImageBox.SizeChanged += new EventHandler(this.ImageBox_SizeChanged); 1452 | this.ImageBox.MouseDown += new MouseEventHandler(this.ImageBox_MouseDown); 1453 | this.ImageBox.MouseUp += new MouseEventHandler(this.ImageBox_MouseUp); 1454 | this.ImageBox.MouseMove += new MouseEventHandler(this.ImageBox_MouseMove); 1455 | 1456 | this.CreateScroll(); 1457 | this.CreateArrow(); 1458 | 1459 | this.BuildTreeRoot(); 1460 | } 1461 | private void ImageBox_SizeChanged(object sender, EventArgs e) 1462 | { 1463 | this.RefreshImage(); 1464 | } 1465 | 1466 | 1467 | 1468 | #region graphique 1469 | 1470 | //juste pour le measure string 1471 | private Graphics zzzggg = Graphics.FromImage(new Bitmap(10, 10)); 1472 | public SizeF MeasureString(string text, Font font) 1473 | { 1474 | return this.zzzggg.MeasureString(text, font); 1475 | } 1476 | 1477 | 1478 | private Font uiItemFont = new Font("consolas", 10f); // 10f 1479 | private float uifItemTextHeight = 20f; // 8f ajusté lors de RefreshImage() 1480 | 1481 | private Pen uiHighlightPen = Pens.White; //pen utilisé pour dessiner un rectangle qui indique quel est l'élément actuellement focusé, ou juste "actuel" 1482 | private Brush uiFolderBackBrush = new SolidBrush(Color.FromArgb(96, 96, 0)); //brush utilisé pour filler l'arrière plan du nom d'un dossier 1483 | 1484 | private int uiDiagSpace = 18; // 18 distance horizontale et vertical à parcourir pour passer immédiatement au prochain enfant en diagonalde 1485 | 1486 | 1487 | 1488 | private Point RootDrawPos = new Point(0, 0); //position graphique, relative au milieu de l'écran, à laquelle dessiner la root 1489 | 1490 | 1491 | public void RefreshImage() 1492 | { 1493 | int imgWidth = this.Width; 1494 | int imgHeight = this.Height; 1495 | if (imgWidth < 50) { imgWidth = 50; } 1496 | if (imgHeight < 50) { imgHeight = 50; } 1497 | Bitmap img = new Bitmap(imgWidth, imgHeight); 1498 | Graphics g = Graphics.FromImage(img); 1499 | g.Clear(Color.FromArgb(16, 16, 16)); 1500 | 1501 | Point ppMiddle = new Point(imgWidth / 2, imgHeight / 2); 1502 | PointF fppMiddle = new PointF((float)imgWidth / 2f, (float)imgHeight / 2f); 1503 | 1504 | //obtien la hauteur du text d'un item 1505 | float fItemTextHeight = this.MeasureString("asdfgathSTHS", this.uiItemFont).Height; 1506 | this.uifItemTextHeight = fItemTextHeight; 1507 | 1508 | 1509 | //////dessine l'objet actuellement sélectionné 1510 | //dessine le text. il faut ajouter le + ou le - si c'est un dossier 1511 | //string strSelectedObjectText = this.ActualElement.UiName; 1512 | //if (this.ActualElement.IsFolder) 1513 | //{ 1514 | // if (ActualElement.IsOpen) 1515 | // { 1516 | // strSelectedObjectText = "-" + strSelectedObjectText; 1517 | // } 1518 | // else { strSelectedObjectText = "+" + strSelectedObjectText; } 1519 | //} 1520 | ////dessine le text de l'objet sélectionné, à sa place, au milieu 1521 | //g.DrawString(strSelectedObjectText, this.uiItemFont, Brushes.White, fppMiddle.X, fppMiddle.Y - (fItemTextHeight / 2f)); 1522 | 1523 | 1524 | 1525 | this.Root.DrawFullAt(ppMiddle.X + this.RootDrawPos.X, ppMiddle.Y + this.RootDrawPos.Y, g); 1526 | 1527 | 1528 | //g.DrawImage(this.GetArrowAtAngle(45d), 0, 0); 1529 | 1530 | 1531 | 1532 | //////fin 1533 | g.Dispose(); 1534 | if (this.ImageBox.Image != null) { this.ImageBox.Image.Dispose(); } 1535 | this.ImageBox.Image = img; 1536 | this.ImageBox.Refresh(); 1537 | } 1538 | 1539 | 1540 | #endregion 1541 | 1542 | } 1543 | } 1544 | -------------------------------------------------------------------------------- /TreeView8.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Drawing; 7 | using System.Windows.Forms; 8 | 9 | namespace FileExplorer4DirectionTreeView 10 | { 11 | public class TreeView8 12 | { 13 | private Point MousePos { get { return this.ImageBox.PointToClient(Cursor.Position); } } 14 | private Rectangle MouseRec { get { return new Rectangle(this.MousePos, new Size(1, 1)); } } 15 | 16 | private PictureBox ImageBox; 17 | 18 | public Control Parent 19 | { 20 | get { return this.ImageBox.Parent; } 21 | set { this.ImageBox.Parent = value; } 22 | } 23 | public int Top 24 | { 25 | get { return this.ImageBox.Top; } 26 | set { this.ImageBox.Top = value; } 27 | } 28 | public int Left 29 | { 30 | get { return this.ImageBox.Left; } 31 | set { this.ImageBox.Left = value; } 32 | } 33 | public int Width 34 | { 35 | get { return this.ImageBox.Width; } 36 | set { this.ImageBox.Width = value; } 37 | } 38 | public int Height 39 | { 40 | get { return this.ImageBox.Height; } 41 | set { this.ImageBox.Height = value; } 42 | } 43 | public AnchorStyles Anchor 44 | { 45 | get { return this.ImageBox.Anchor; } 46 | set { this.ImageBox.Anchor = value; } 47 | } 48 | public DockStyle Dock 49 | { 50 | get { return this.ImageBox.Dock; } 51 | set { this.ImageBox.Dock = value; } 52 | } 53 | 54 | 55 | 56 | 57 | #region gestion de l'arbre 58 | 59 | private TreeObject Root = null; 60 | private TreeObject ActualElement = null; //élément actuellement sélectionné par l'user 61 | 62 | 63 | 64 | 65 | private enum TOType 66 | { 67 | folder, 68 | file 69 | } 70 | private enum Dir 71 | { 72 | Up, // 1 73 | UpLeft, // 4 74 | Left, // 7 75 | DownLeft, // 2 76 | Down, // 5 77 | DownRight, // 8 78 | Right, // 3 79 | UpRight, // 6 80 | } 81 | private class TreeObject 82 | { 83 | private TreeView8 TV = null; 84 | 85 | public TreeObject Parent = null; 86 | public Dir dir = Dir.DownLeft; 87 | public void SetThisAsNextDirectionOf(Dir d) 88 | { 89 | if (d == Dir.Up) { this.dir = Dir.DownLeft; } 90 | else if (d == Dir.DownLeft) { this.dir = Dir.Right; } 91 | else if (d == Dir.Right) { this.dir = Dir.UpLeft; } 92 | else if (d == Dir.UpLeft) { this.dir = Dir.Down; } 93 | else if (d == Dir.Down) { this.dir = Dir.UpRight; } 94 | else if (d == Dir.UpRight) { this.dir = Dir.Left; } 95 | else if (d == Dir.Left) { this.dir = Dir.DownRight; } 96 | else if (d == Dir.DownRight) { this.dir = Dir.Up; } 97 | } 98 | 99 | public TOType tType = TOType.file; 100 | public bool IsFolder { get { return this.tType == TOType.folder; } } 101 | public string Path = ""; 102 | public string UiName = ""; 103 | 104 | 105 | //text finale à afficher. si this est un dossier, il contiendra déjà le + ou le - au bon endroit. 106 | public string UiFinalText 107 | { 108 | get 109 | { 110 | //si this est un dossier, il faut rajouter le + ou le - au bon endroit 111 | if (this.IsFolder) 112 | { 113 | string c = "+"; 114 | if (this.IsOpen) { c = "-"; } 115 | 116 | //si le caractère va au début 117 | if (this.dir == Dir.Right || this.dir == Dir.UpRight || this.dir == Dir.Up || this.dir == Dir.UpLeft) 118 | { 119 | return c + this.UiName; 120 | } 121 | 122 | //si le caractère va à la fin 123 | return this.UiName + c; 124 | } 125 | 126 | //si this est un fichier 127 | return this.UiName; 128 | } 129 | } 130 | private float zzzUiFinalTextWidth = 0f; //laugeur graphique du text finale s'il est parallère "au sol". 131 | private float UiFinalTextWidth 132 | { 133 | get 134 | { 135 | if (!this.isFinalComputed) { this.ComputeFinal(); } 136 | return this.zzzUiFinalTextWidth; 137 | } 138 | } 139 | private bool isFinalComputed = false; 140 | private void ComputeFinal() 141 | { 142 | this.zzzUiFinalTextWidth = this.TV.MeasureString(this.UiFinalText, this.TV.uiItemFont).Width; 143 | this.isFinalComputed = true; 144 | } 145 | 146 | 147 | 148 | //folder properties 149 | public bool NotEmpty { get { return this.listChild.Count > 0; } } 150 | public bool IsOpen = false; //indique si le dossier est actuellement ouvert ou fermé à l'écran 151 | public bool ChildLoaded = false; //indique si les élément enfant de this ont été chargé dans la liste des enfant de this 152 | public List listChild = new List(); 153 | 154 | public void LoadChild() 155 | { 156 | if (this.IsFolder && !this.ChildLoaded) 157 | { 158 | //load les dossier 159 | try 160 | { 161 | string[] arFolders = System.IO.Directory.GetDirectories(this.Path); 162 | foreach (string FolderPath in arFolders) 163 | { 164 | TreeObject newto = new TreeObject(this, FolderPath, TOType.folder, this.TV); 165 | } 166 | 167 | //load les fichier 168 | string[] arFiles = System.IO.Directory.GetFiles(this.Path); 169 | foreach (string FilePath in arFiles) 170 | { 171 | TreeObject newto = new TreeObject(this, FilePath, TOType.file, this.TV); 172 | } 173 | } 174 | catch { } 175 | 176 | this.ChildLoaded = true; 177 | } 178 | } 179 | public void Open() 180 | { 181 | if (this.IsFolder) 182 | { 183 | //make sure que les enfant ont été loadé 184 | if (!this.ChildLoaded) { this.LoadChild(); } 185 | this.IsOpen = true; 186 | } 187 | } 188 | public void Close() 189 | { 190 | if (this.IsFolder) 191 | { 192 | this.IsOpen = false; 193 | } 194 | } 195 | public void RecursiveCloseAll() 196 | { 197 | if (this.IsFolder && this.IsOpen) 198 | { 199 | foreach (TreeObject to in this.listChild) 200 | { 201 | to.RecursiveCloseAll(); 202 | } 203 | this.Close(); 204 | } 205 | } 206 | public void RecursiveOpenAll() 207 | { 208 | if (this.IsFolder) 209 | { 210 | if (!this.IsOpen) 211 | { 212 | this.Open(); 213 | } 214 | foreach (TreeObject to in this.listChild) 215 | { 216 | to.RecursiveOpenAll(); 217 | } 218 | } 219 | } 220 | 221 | 222 | //ce constructeur c'est pour quand les propriété sont défini depuis l'extérieur après l'initialisation de l'object 223 | public TreeObject(string sUiName, TreeView8 sTV) 224 | { 225 | this.TV = sTV; 226 | 227 | this.Parent = null; 228 | this.Path = ""; 229 | this.UiName = sUiName; 230 | } 231 | //this s'ajoute automatiquement au parent spécifié 232 | public TreeObject(TreeObject sParent, string sPath, TOType stType, TreeView8 sTV) 233 | { 234 | this.TV = sTV; 235 | 236 | this.Parent = sParent; 237 | if (sParent != null) 238 | { 239 | sParent.listChild.Add(this); 240 | this.SetThisAsNextDirectionOf(sParent.dir); 241 | } 242 | 243 | this.Path = sPath; 244 | this.UiName = System.IO.Path.GetFileName(sPath); 245 | 246 | this.tType = stType; 247 | 248 | } 249 | 250 | 251 | 252 | 253 | 254 | //dessine le nom et les sous objet de this à partir de la coordonné graphique spécifié 255 | public void DrawFullAt(float uix, float uiy, Graphics g) 256 | { 257 | 258 | this.DrawNameAt(uix, uiy, g); 259 | 260 | //si this est un folder et qu'il est ouvert, on dessine la racine 261 | if (this.IsFolder) 262 | { 263 | if (this.IsOpen) 264 | { 265 | float R2 = (float)(Math.Sqrt(2d)); 266 | float rooty = uiy; 267 | float rootx = uix + this.UiFinalTextWidth; //si le text est à dessiner horizontalement à droite 268 | 269 | //si le text est à dessiner horizontalement à gauche 270 | if (this.dir == Dir.DownLeft || this.dir == Dir.Down || this.dir == Dir.Left) 271 | { 272 | rootx = uix - this.UiFinalTextWidth; 273 | } 274 | 275 | //les cas où le text est à dessiner en diagonalde 276 | if (this.dir == Dir.UpLeft) 277 | { 278 | rooty = uiy - (this.UiFinalTextWidth / R2); 279 | rootx = uix + (this.UiFinalTextWidth / R2); 280 | } 281 | if (this.dir == Dir.DownRight) 282 | { 283 | rooty = uiy + (this.UiFinalTextWidth / R2); 284 | rootx = uix - (this.UiFinalTextWidth / R2); 285 | } 286 | 287 | 288 | 289 | 290 | //on fait dessiner la root à l'endroit prévu 291 | this.DrawSubAt(rootx, rooty, g); 292 | 293 | 294 | 295 | ////on rajoute le caractère "+" à UiName parce que les dossier ont toujours un caractère de plus qui indique s'ils sont ouvert ou fermé 296 | //int namewidth = (int)(g.MeasureString(this.UiName + "+", this.TV.uiItemFont).Width); 297 | ////position horizontal à laquelle dessiner la root 298 | //int rootx = uix + namewidth; 299 | //if (this.dir == Dir.UpLeft || this.dir == Dir.DownLeft) { rootx = uix - namewidth; } 300 | 301 | ////fait dessiner la root à l'endroit prévu 302 | //this.DrawSubAt(rootx, uiy, g); 303 | 304 | } 305 | } 306 | 307 | 308 | } 309 | 310 | 311 | //basé sur les dernière coordonné graphique enregistré, retourne l'objet situé sous la coordonné graphique spécifié 312 | public TreeObject GetObjetUnderPos(int uix, int uiy) 313 | { 314 | //on commence par checker this. le vérification sera différente si le parent se déroule dans une direction horizontale (Right, Left) parce que dans ce cas les éléments sont dessinés en diagonalde 315 | if (this.dir != Dir.UpLeft && this.dir != Dir.DownRight) 316 | { 317 | if (this.uiLastX <= uix && uix < this.uiLastX + this.uiLastWidth) 318 | { 319 | if (this.uiLastY <= uiy && uiy < this.uiLastY + (int)(this.TV.uifItemTextHeight)) 320 | { 321 | return this; 322 | } 323 | } 324 | } 325 | else //this est dessiné en diagonalde 326 | { 327 | //le parent va vers la droite ou vers la gauche 328 | if (this.dir == Dir.UpLeft || this.dir == Dir.DownRight) 329 | { 330 | int x1 = this.uiLastX; 331 | int y1 = this.uiLastY; 332 | int x2 = uix; 333 | int y2 = uiy; 334 | float R2 = (float)(Math.Sqrt(2d)); 335 | 336 | if (y2 - y1 <= x2 - x1) 337 | { 338 | if (y2 - y1 >= x2 - x1 - (int)(2f * (float)(this.uiLastWidth) / R2)) 339 | { 340 | if (Math.Abs(x2 - ((x1 + x2 + y1 - y2) / 2)) <= (int)(this.TV.uifItemTextHeight / 2f / R2)) 341 | { 342 | return this; 343 | } 344 | } 345 | } 346 | 347 | } 348 | } 349 | 350 | //si this est un dossier OUVERT, il faut maintenant vérifier les enfant 351 | if (this.IsFolder && this.IsOpen) 352 | { 353 | foreach (TreeObject to in this.listChild) 354 | { 355 | TreeObject torep = to.GetObjetUnderPos(uix, uiy); 356 | if (torep != null) { return torep; } 357 | } 358 | } 359 | 360 | return null; 361 | } 362 | 363 | //coordonné graphique du text en haut à gauche lors du dernier refresh graphique. mis à jour par DrawNameAt(,,) 364 | public int uiLastX = 0; 365 | public int uiLastY = 0; 366 | public int uiLastWidth = 0; 367 | 368 | //dessine le nom de this à partir de la coordonné graphique spécifié 369 | public void DrawNameAt(float uix, float uiy, Graphics g) 370 | { 371 | float uifItemTextHeight = this.TV.uifItemTextHeight; 372 | float R2 = this.TV.Sqrt2; 373 | 374 | //si le nom est à dessiner à droite 375 | if (this.dir == Dir.Up || this.dir == Dir.Right || this.dir == Dir.UpRight) 376 | { 377 | // Rectangle NameRect = new Rectangle(uix, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 378 | Rectangle NameRect = new Rectangle((int)uix, (int)(uiy - (uifItemTextHeight / 2f)), (int)(this.UiFinalTextWidth), (int)uifItemTextHeight); 379 | this.uiLastX = NameRect.X; 380 | this.uiLastY = NameRect.Y; 381 | this.uiLastWidth = NameRect.Width; 382 | 383 | //on dessine l'item 384 | if (this.IsFolder) 385 | { 386 | g.FillRectangle(this.TV.uiFolderBackBrush, NameRect); 387 | } 388 | g.DrawString(this.UiFinalText, this.TV.uiItemFont, Brushes.White, uix, uiy - (uifItemTextHeight / 2f)); 389 | 390 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 391 | if (this.TV.ActualElement == this) 392 | { 393 | g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 394 | } 395 | 396 | } 397 | 398 | //si le nom est à dessiner à gauche 399 | if (this.dir == Dir.DownLeft || this.dir == Dir.Down || this.dir == Dir.Left) 400 | { 401 | // Rectangle NameRect = new Rectangle(uix, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 402 | Rectangle NameRect = new Rectangle((int)(uix - this.UiFinalTextWidth), (int)(uiy - (uifItemTextHeight / 2f)), (int)(this.UiFinalTextWidth), (int)uifItemTextHeight); 403 | this.uiLastX = NameRect.X; 404 | this.uiLastY = NameRect.Y; 405 | this.uiLastWidth = NameRect.Width; 406 | 407 | //on dessine l'item 408 | if (this.IsFolder) 409 | { 410 | g.FillRectangle(this.TV.uiFolderBackBrush, NameRect); 411 | } 412 | g.DrawString(this.UiFinalText, this.TV.uiItemFont, Brushes.White, uix - this.UiFinalTextWidth, uiy - (uifItemTextHeight / 2f)); 413 | 414 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 415 | if (this.TV.ActualElement == this) 416 | { 417 | g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 418 | } 419 | 420 | } 421 | 422 | 423 | //si le nom est à dessiner en diagonalde, le parent défile vers la droite 424 | if (this.dir == Dir.UpLeft) 425 | { 426 | // Rectangle NameRect = new Rectangle(uix, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 427 | Rectangle NameRect = new Rectangle((int)(uix), (int)(uiy - (uifItemTextHeight / 2f)), (int)(this.UiFinalTextWidth), (int)uifItemTextHeight); 428 | this.uiLastX = NameRect.X; 429 | this.uiLastY = NameRect.Y + (int)(uifItemTextHeight / 2f); //il faut ajouter la moitier de la hauteur parce que la formule qui calcul si un point est dessus un rectangle en angle de 45 considère que la coordonné (x,y) est le milieu du côté vertical, et Non le coin supérieur gauche 430 | this.uiLastWidth = NameRect.Width; 431 | 432 | 433 | g.TranslateTransform(uix, uiy); 434 | g.RotateTransform(-45f); 435 | 436 | //on dessine l'item 437 | if (this.IsFolder) 438 | { 439 | g.FillRectangle(this.TV.uiFolderBackBrush, 0, (int)(-NameRect.Height / 2f), (int)(NameRect.Width), (int)(NameRect.Height)); 440 | } 441 | g.DrawString(this.UiFinalText, this.TV.uiItemFont, Brushes.White, 0f, uifItemTextHeight / -2f); 442 | 443 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 444 | if (this.TV.ActualElement == this) 445 | { 446 | g.DrawRectangle(this.TV.uiHighlightPen, 0, (int)(-NameRect.Height / 2f), (int)(NameRect.Width), (int)(NameRect.Height)); 447 | } 448 | 449 | g.ResetTransform(); 450 | 451 | } 452 | //si le nom est à dessiner en diagonalde, le parent défile vers la gauche 453 | if (this.dir == Dir.DownRight) 454 | { 455 | // Rectangle NameRect = new Rectangle(uix, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 456 | Rectangle NameRect = new Rectangle((int)(uix), (int)(uiy - (uifItemTextHeight / 2f)), (int)(this.UiFinalTextWidth), (int)uifItemTextHeight); 457 | this.uiLastX = NameRect.X - (int)(this.UiFinalTextWidth / R2); 458 | this.uiLastY = NameRect.Y + (int)(this.UiFinalTextWidth / R2) + (int)(uifItemTextHeight / 2f); //il faut ajouter la moitier de la hauteur parce que la formule qui calcul si un point est dessus un rectangle en angle de 45 considère que la coordonné (x,y) est le milieu du côté vertical, et Non le coin supérieur gauche 459 | this.uiLastWidth = NameRect.Width; 460 | 461 | 462 | g.TranslateTransform(uix, uiy); 463 | g.RotateTransform(-45f); 464 | 465 | //on dessine l'item 466 | if (this.IsFolder) 467 | { 468 | g.FillRectangle(this.TV.uiFolderBackBrush, -(NameRect.Width), (int)(-NameRect.Height / 2f), (int)(NameRect.Width), (int)(NameRect.Height)); 469 | } 470 | g.DrawString(this.UiFinalText, this.TV.uiItemFont, Brushes.White, -this.UiFinalTextWidth, uifItemTextHeight / -2f); 471 | 472 | //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 473 | if (this.TV.ActualElement == this) 474 | { 475 | g.DrawRectangle(this.TV.uiHighlightPen, (int)(-NameRect.Width), (int)(-NameRect.Height / 2f), (int)(NameRect.Width), (int)(NameRect.Height)); 476 | } 477 | 478 | g.ResetTransform(); 479 | 480 | } 481 | 482 | 483 | 484 | ////fait la chaine de text qui est le nom 485 | //string strName = this.UiName; 486 | 487 | ////dessine le nom selon que le nom doit aller à droite ou à gauche 488 | //if (this.dir == Dir.DownRight || this.dir == Dir.UpRight) 489 | //{ 490 | // //on ajoute le caractère qui indique si le dossier est ouvert ou fermé 491 | // if (this.IsOpen) 492 | // { 493 | // strName = "-" + strName; 494 | // } 495 | // else { strName = "+" + strName; } 496 | 497 | // //on obtien le rectangle d'arrière plan 498 | // int NameWidth = (int)(g.MeasureString(strName, this.TV.uiItemFont).Width); 499 | // Rectangle NameRect = new Rectangle(uix, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 500 | // //sauvgarde la position 501 | // this.uiLastX = NameRect.X; 502 | // this.uiLastY = NameRect.Y; 503 | // this.uiLastWidth = NameRect.Width; 504 | 505 | // //on remplit l'arrière plan 506 | // g.FillRectangle(this.TV.uiFolderBackBrush, NameRect); 507 | 508 | // //on le dessine à droite 509 | // g.DrawString(strName, this.TV.uiItemFont, Brushes.White, (float)uix, (float)uiy - (this.TV.uifItemTextHeight / 2f)); 510 | 511 | // //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 512 | // if (this.TV.ActualElement == this) 513 | // { 514 | // g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 515 | // } 516 | 517 | //} 518 | //if (this.dir == Dir.UpLeft || this.dir == Dir.DownLeft) 519 | //{ 520 | // //on ajoute le caractère qui indique si le dossier est ouvert ou fermé 521 | // if (this.IsOpen) 522 | // { 523 | // strName += "-"; 524 | // } 525 | // else { strName += "+"; } 526 | 527 | // //on obtien le rectangle d'arrière plan 528 | // int NameWidth = (int)(g.MeasureString(strName, this.TV.uiItemFont).Width); 529 | // Rectangle NameRect = new Rectangle(uix - NameWidth, uiy - (int)(this.TV.uifItemTextHeight / 2f), NameWidth, (int)(this.TV.uifItemTextHeight)); 530 | // //sauvgarde la position 531 | // this.uiLastX = NameRect.X; 532 | // this.uiLastY = NameRect.Y; 533 | // this.uiLastWidth = NameRect.Width; 534 | 535 | // //on remplit l'arrière plan 536 | // g.FillRectangle(this.TV.uiFolderBackBrush, NameRect); 537 | 538 | // //on le dessine à gauche 539 | // g.DrawString(strName, this.TV.uiItemFont, Brushes.White, (float)(uix - NameWidth), (float)uiy - (this.TV.uifItemTextHeight / 2f)); 540 | 541 | // //si this est l'élément actuellement sélectionné par l'user, on dessine un rectangle autour du nom 542 | // if (this.TV.ActualElement == this) 543 | // { 544 | // g.DrawRectangle(this.TV.uiHighlightPen, NameRect); 545 | // } 546 | 547 | //} 548 | 549 | 550 | } 551 | 552 | //si this est un dossier, dessine tout les élément enfant, dans la bonne direction, en commencant la racine à l'endroit spécifié 553 | public void DrawSubAt(float uix, float uiy, Graphics g) 554 | { 555 | if (this.IsFolder) 556 | { 557 | Pen penLineDiag = new Pen(Color.Silver, 2f); //Pens.Silver; 558 | float uiDiagSpace = this.TV.uiDiagSpace; 559 | float R2 = this.TV.Sqrt2; 560 | 561 | 562 | if (this.dir == Dir.Up) 563 | { 564 | float ActualUiX = uix; 565 | float ActualUiY = uiy; 566 | 567 | float CurrentMaxHWidth = this.UiFinalTextWidth - uiDiagSpace; //width horizontale maximal actuel 568 | 569 | float AccumulatedDownHeight = 0f; 570 | 571 | int index = 0; 572 | while (index < this.listChild.Count) 573 | { 574 | TreeObject to = this.listChild[index]; 575 | //on monte d'un item et on dessine la ligne 576 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX, ActualUiY - uiDiagSpace); 577 | ActualUiY -= uiDiagSpace; 578 | CurrentMaxHWidth += uiDiagSpace; 579 | 580 | 581 | //check si c'est un dossier ouvert 582 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 583 | { 584 | //on ajoute le upheight et le accumulated down height 585 | float dist = to.UpHeight + AccumulatedDownHeight; 586 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX, ActualUiY - dist); 587 | ActualUiY -= dist; 588 | CurrentMaxHWidth += dist; 589 | 590 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 591 | } 592 | 593 | //on check si l'élément est trop large pour la hauteur actuel 594 | float diff = (2f * to.Width / R2) - to.DownHeight - CurrentMaxHWidth + uiDiagSpace; 595 | if (diff > 0f) 596 | { 597 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX, ActualUiY - diff); 598 | ActualUiY -= diff; 599 | CurrentMaxHWidth += diff; 600 | } 601 | 602 | //le down height accumulé diminue 603 | AccumulatedDownHeight -= uiDiagSpace; 604 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 605 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 606 | 607 | 608 | ////on fait dessiner l'item 609 | //on dessine une "coche" 610 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY); 611 | 612 | //on lui fait dessiner l'item 613 | to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY, g); 614 | 615 | 616 | //next iteration 617 | index++; 618 | } 619 | 620 | } 621 | else if (this.dir == Dir.DownLeft) 622 | { 623 | 624 | float ActualUiX = uix; 625 | float ActualUiY = uiy; 626 | 627 | float CurrentMaxEffectiveWidth = this.UiFinalTextWidth - uiDiagSpace; //largeur actuel maximal d'un élément. augmente en descendant 628 | 629 | int index = 0; 630 | while (index < this.listChild.Count) 631 | { 632 | TreeObject to = this.listChild[index]; 633 | //on descend d'un item et on dessine la ligne 634 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY + uiDiagSpace); 635 | ActualUiX -= uiDiagSpace; 636 | ActualUiY += uiDiagSpace; 637 | CurrentMaxEffectiveWidth += uiDiagSpace; 638 | 639 | 640 | ////analyse s'il faut descendre encore d'avantage parce que l'élément est trop large pour la hauteur actuel 641 | //if (to.Width > CurrentMaxWidth) 642 | //{ 643 | // //on se déplace de la distance nécessaire pour que l'élément puisse rentrer au complet 644 | // float diff = to.Width - CurrentMaxWidth; 645 | // g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - diff, ActualUiY + diff); 646 | // ActualUiX -= diff; 647 | // ActualUiY += diff; 648 | // CurrentMaxWidth += diff; 649 | //} 650 | 651 | //si c'est un dossier ouvert 652 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 653 | { 654 | //on doit descendre de la hauteur du upheight 655 | float uhdr2 = to.UpHeight / R2; 656 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uhdr2, ActualUiY + uhdr2); 657 | ActualUiX -= uhdr2; 658 | ActualUiY += uhdr2; 659 | CurrentMaxEffectiveWidth += uhdr2; 660 | } 661 | 662 | //on check si le effective width et trop grand pour la hauteur actuel 663 | if (to.EffectiveWidth > CurrentMaxEffectiveWidth) 664 | { 665 | float diff = to.EffectiveWidth - CurrentMaxEffectiveWidth; 666 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - diff, ActualUiY + diff); 667 | ActualUiX -= diff; 668 | ActualUiY += diff; 669 | CurrentMaxEffectiveWidth += diff; 670 | } 671 | 672 | 673 | 674 | ////on fait dessiner l'item 675 | //on dessine une "coche" 676 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY); 677 | 678 | //on lui fait dessiner l'item 679 | to.DrawFullAt(ActualUiX + uiDiagSpace, ActualUiY, g); 680 | 681 | 682 | //next iteration 683 | index++; 684 | } 685 | 686 | 687 | } 688 | else if (this.dir == Dir.Right) 689 | { 690 | float ActualUiX = uix; 691 | float ActualUiY = uiy; 692 | 693 | 694 | //le downheight accumulé précédement. lorsqu'un dossier est ouvert, il doit se déplacer de cette distance pour ne pas chevaucher les dossier précédant 695 | float AccumulatedDownHeight = 0f; 696 | 697 | //hauteur graphique maximal actuel 698 | float CurrentMaxVWidth = this.UiFinalTextWidth - (uiDiagSpace * R2); 699 | 700 | int index = 0; 701 | while (index < this.listChild.Count) 702 | { 703 | TreeObject to = this.listChild[index]; 704 | //on descend d'un item et on dessine la ligne 705 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + (uiDiagSpace * R2), ActualUiY); 706 | ActualUiX += uiDiagSpace * R2; 707 | CurrentMaxVWidth += uiDiagSpace * R2; 708 | 709 | 710 | //on check si c'est un dossier ouvert 711 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 712 | { 713 | //on doit ajouter le upheight et le accumulated down height 714 | float dist = to.UpHeight + AccumulatedDownHeight; 715 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + dist, ActualUiY); 716 | ActualUiX += dist; 717 | CurrentMaxVWidth += dist; 718 | 719 | //on check si le dossier a trop de width verticale pour la position horizontale actuel 720 | float diff = (to.Width / R2) - to.DownHeight + to.EffectiveWidth - CurrentMaxVWidth; //(2f * to.Width / R2) + to.UpHeight - CurrentMaxVWidth; 721 | if (diff > 0f) 722 | { 723 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + diff, ActualUiY); 724 | ActualUiX += diff; 725 | CurrentMaxVWidth += diff; 726 | } 727 | 728 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 729 | } 730 | 731 | //le down height accumulé diminue 732 | AccumulatedDownHeight -= uiDiagSpace * R2; 733 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 734 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 735 | 736 | 737 | ////on fait dessiner l'item 738 | //on dessine une "coche" 739 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY - uiDiagSpace); 740 | 741 | //on lui fait dessiner l'item 742 | to.DrawFullAt(ActualUiX + uiDiagSpace, ActualUiY - uiDiagSpace, g); 743 | 744 | 745 | //next iteration 746 | index++; 747 | } 748 | } 749 | else if (this.dir == Dir.UpLeft) 750 | { 751 | float ActualUiX = uix; 752 | float ActualUiY = uiy; 753 | 754 | float AccumulatedDownHeight = 0f; //le down height qui a été accumulé depuis les élément précédant 755 | 756 | float CurrentMaxWidth = 0f; //le width (ici c'est à la verticale) maximale qu'un dossier peut avoir à chaque instant 757 | 758 | int index = 0; 759 | while (index < this.listChild.Count) 760 | { 761 | TreeObject to = this.listChild[index]; 762 | //on monte d'un item et on dessine la ligne 763 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY - uiDiagSpace); 764 | ActualUiX -= uiDiagSpace; 765 | ActualUiY -= uiDiagSpace; 766 | CurrentMaxWidth += uiDiagSpace; 767 | 768 | 769 | //on check si c'est un dossier ouvert 770 | if (to.IsFolder & to.IsOpen && to.NotEmpty) 771 | { 772 | 773 | //on ajoute le upheight et le accumulated down height 774 | float dist = to.UpHeight + AccumulatedDownHeight; 775 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - (dist / R2), ActualUiY - (dist / R2)); 776 | ActualUiX -= dist / R2; 777 | ActualUiY -= dist / R2; 778 | CurrentMaxWidth += dist / R2; 779 | 780 | //on check si le dossier a trop de width pour la hauteur actuel 781 | float diff = (to.Width - (to.DownHeight / R2)) - CurrentMaxWidth; 782 | if (diff > 0f) 783 | { 784 | //on se déplace d'autant qu'il le faut pour que le dossier ait assez de place 785 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - diff, ActualUiY - diff); 786 | ActualUiX -= diff; 787 | ActualUiY -= diff; 788 | CurrentMaxWidth += diff; 789 | } 790 | 791 | 792 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 793 | } 794 | 795 | //le down height accumulé diminue 796 | AccumulatedDownHeight -= uiDiagSpace * R2; 797 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 798 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 799 | 800 | 801 | ////on fait dessiner l'item 802 | //on dessine une "coche" 803 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY); 804 | 805 | //on lui fait dessiner l'item 806 | to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY, g); 807 | 808 | 809 | //next iteration 810 | index++; 811 | } 812 | 813 | 814 | 815 | } 816 | else if (this.dir == Dir.Down) 817 | { 818 | float ActualUiX = uix; 819 | float ActualUiY = uiy; 820 | 821 | float CurrentMaxHWidth = this.UiFinalTextWidth - uiDiagSpace; //width horizontale maximal actuel 822 | 823 | float AccumulatedDownHeight = 0f; 824 | 825 | int index = 0; 826 | while (index < this.listChild.Count) 827 | { 828 | TreeObject to = this.listChild[index]; 829 | //on monte d'un item et on dessine la ligne 830 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX, ActualUiY + uiDiagSpace); 831 | ActualUiY += uiDiagSpace; 832 | CurrentMaxHWidth += uiDiagSpace; 833 | 834 | 835 | //check si c'est un dossier ouvert 836 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 837 | { 838 | //on ajoute le upheight et le accumulated down height 839 | float dist = to.UpHeight + AccumulatedDownHeight; 840 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX, ActualUiY + dist); 841 | ActualUiY += dist; 842 | CurrentMaxHWidth += dist; 843 | 844 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 845 | } 846 | 847 | //on check si l'élément est trop large pour la hauteur actuel 848 | float diff = (2f * to.Width / R2) - to.DownHeight - CurrentMaxHWidth + uiDiagSpace; 849 | if (diff > 0f) 850 | { 851 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX, ActualUiY + diff); 852 | ActualUiY += diff; 853 | CurrentMaxHWidth += diff; 854 | } 855 | 856 | //le down height accumulé diminue 857 | AccumulatedDownHeight -= uiDiagSpace; 858 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 859 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 860 | 861 | 862 | ////on fait dessiner l'item 863 | //on dessine une "coche" 864 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY); 865 | 866 | //on lui fait dessiner l'item 867 | to.DrawFullAt(ActualUiX + uiDiagSpace, ActualUiY, g); 868 | 869 | 870 | //next iteration 871 | index++; 872 | } 873 | } 874 | else if (this.dir == Dir.UpRight) 875 | { 876 | 877 | float ActualUiX = uix; 878 | float ActualUiY = uiy; 879 | 880 | float CurrentMaxEffectiveWidth = this.UiFinalTextWidth - uiDiagSpace; //largeur actuel maximal d'un élément. augmente en descendant 881 | 882 | int index = 0; 883 | while (index < this.listChild.Count) 884 | { 885 | TreeObject to = this.listChild[index]; 886 | //on descend d'un item et on dessine la ligne 887 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY - uiDiagSpace); 888 | ActualUiX += uiDiagSpace; 889 | ActualUiY -= uiDiagSpace; 890 | CurrentMaxEffectiveWidth += uiDiagSpace; 891 | 892 | 893 | //si c'est un dossier ouvert 894 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 895 | { 896 | //on doit descendre de la hauteur du upheight 897 | float uhdr2 = to.UpHeight / R2; 898 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uhdr2, ActualUiY - uhdr2); 899 | ActualUiX += uhdr2; 900 | ActualUiY -= uhdr2; 901 | CurrentMaxEffectiveWidth += uhdr2; 902 | } 903 | 904 | //on check si le effective width et trop grand pour la hauteur actuel 905 | if (to.EffectiveWidth > CurrentMaxEffectiveWidth) 906 | { 907 | float diff = to.EffectiveWidth - CurrentMaxEffectiveWidth; 908 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + diff, ActualUiY - diff); 909 | ActualUiX += diff; 910 | ActualUiY -= diff; 911 | CurrentMaxEffectiveWidth += diff; 912 | } 913 | 914 | 915 | 916 | ////on fait dessiner l'item 917 | //on dessine une "coche" 918 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY); 919 | 920 | //on lui fait dessiner l'item 921 | to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY, g); 922 | 923 | 924 | //next iteration 925 | index++; 926 | } 927 | 928 | 929 | 930 | } 931 | else if (this.dir == Dir.Left) 932 | { 933 | float ActualUiX = uix; 934 | float ActualUiY = uiy; 935 | 936 | //le downheight accumulé précédement. lorsqu'un dossier est ouvert, il doit se déplacer de cette distance pour ne pas chevaucher les dossier précédant 937 | float AccumulatedDownHeight = 0f; 938 | 939 | //hauteur graphique maximal actuel 940 | float CurrentMaxVWidth = this.UiFinalTextWidth - (uiDiagSpace * R2); 941 | 942 | int index = 0; 943 | while (index < this.listChild.Count) 944 | { 945 | TreeObject to = this.listChild[index]; 946 | //on descend d'un item et on dessine la ligne 947 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - (uiDiagSpace * R2), ActualUiY); 948 | ActualUiX -= uiDiagSpace * R2; 949 | CurrentMaxVWidth += uiDiagSpace * R2; 950 | 951 | 952 | //on check si c'est un dossier ouvert 953 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 954 | { 955 | //on doit ajouter le upheight et le accumulated down height 956 | float dist = to.UpHeight + AccumulatedDownHeight; 957 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - dist, ActualUiY); 958 | ActualUiX -= dist; 959 | CurrentMaxVWidth += dist; 960 | 961 | //on check si le dossier a trop de width verticale pour la position horizontale actuel 962 | float diff = (to.Width / R2) - to.DownHeight + to.EffectiveWidth - CurrentMaxVWidth; //(2f * to.Width / R2) + to.UpHeight - CurrentMaxVWidth; 963 | if (diff > 0f) 964 | { 965 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - diff, ActualUiY); 966 | ActualUiX -= diff; 967 | CurrentMaxVWidth += diff; 968 | } 969 | 970 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 971 | } 972 | 973 | //le down height accumulé diminue 974 | AccumulatedDownHeight -= uiDiagSpace * R2; 975 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 976 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 977 | 978 | 979 | ////on fait dessiner l'item 980 | //on dessine une "coche" 981 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX - uiDiagSpace, ActualUiY + uiDiagSpace); 982 | 983 | //on lui fait dessiner l'item 984 | to.DrawFullAt(ActualUiX - uiDiagSpace, ActualUiY + uiDiagSpace, g); 985 | 986 | 987 | //next iteration 988 | index++; 989 | } 990 | } 991 | else if (this.dir == Dir.DownRight) 992 | { 993 | 994 | float ActualUiX = uix; 995 | float ActualUiY = uiy; 996 | 997 | float AccumulatedDownHeight = 0f; //le down height qui a été accumulé depuis les élément précédant 998 | 999 | float CurrentMaxWidth = 0f; //le width (ici c'est à la verticale) maximale qu'un dossier peut avoir à chaque instant 1000 | 1001 | int index = 0; 1002 | while (index < this.listChild.Count) 1003 | { 1004 | TreeObject to = this.listChild[index]; 1005 | //on monte d'un item et on dessine la ligne 1006 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY + uiDiagSpace); 1007 | ActualUiX += uiDiagSpace; 1008 | ActualUiY += uiDiagSpace; 1009 | CurrentMaxWidth += uiDiagSpace; 1010 | 1011 | 1012 | //on check si c'est un dossier ouvert 1013 | if (to.IsFolder & to.IsOpen && to.NotEmpty) 1014 | { 1015 | 1016 | //on ajoute le upheight et le accumulated down height 1017 | float dist = to.UpHeight + AccumulatedDownHeight; 1018 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + (dist / R2), ActualUiY + (dist / R2)); 1019 | ActualUiX += dist / R2; 1020 | ActualUiY += dist / R2; 1021 | CurrentMaxWidth += dist / R2; 1022 | 1023 | //on check si le dossier a trop de width pour la hauteur actuel 1024 | float diff = (to.Width - (to.DownHeight / R2)) - CurrentMaxWidth; 1025 | if (diff > 0f) 1026 | { 1027 | //on se déplace d'autant qu'il le faut pour que le dossier ait assez de place 1028 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + diff, ActualUiY + diff); 1029 | ActualUiX += diff; 1030 | ActualUiY += diff; 1031 | CurrentMaxWidth += diff; 1032 | } 1033 | 1034 | 1035 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 1036 | } 1037 | 1038 | //le down height accumulé diminue 1039 | AccumulatedDownHeight -= uiDiagSpace * R2; 1040 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 1041 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 1042 | 1043 | 1044 | ////on fait dessiner l'item 1045 | //on dessine une "coche" 1046 | g.DrawLine(penLineDiag, ActualUiX, ActualUiY, ActualUiX + uiDiagSpace, ActualUiY); 1047 | 1048 | //on lui fait dessiner l'item 1049 | to.DrawFullAt(ActualUiX + uiDiagSpace, ActualUiY, g); 1050 | 1051 | 1052 | //next iteration 1053 | index++; 1054 | } 1055 | 1056 | 1057 | 1058 | 1059 | } 1060 | 1061 | penLineDiag.Dispose(); 1062 | } 1063 | } 1064 | 1065 | 1066 | 1067 | 1068 | public float Width = 0f; 1069 | public float UpHeight = 0f; 1070 | public float DownHeight = 0f; 1071 | 1072 | //utilisé par les direction left et right 1073 | public float EffectiveWidth = 0f; 1074 | 1075 | 1076 | 1077 | 1078 | public SizeWUD RecursiveComputeSize() 1079 | { 1080 | float aWidth = 0f; 1081 | float aUpHeight = 0f; 1082 | float aDownHeight = 0f; 1083 | float aEffectiveWidth = 0f; 1084 | 1085 | 1086 | float R2 = this.TV.Sqrt2; 1087 | float uiDiagSpace = this.TV.uiDiagSpace; 1088 | 1089 | //check si this est un dossier ouvert 1090 | if (this.IsFolder && this.IsOpen && this.listChild.Count > 0) 1091 | { 1092 | if (this.dir == Dir.Up || this.dir == Dir.Down) 1093 | { 1094 | 1095 | aWidth = this.UiFinalTextWidth; 1096 | 1097 | float height = 0f; //le height total de this est le plus grand width qui existe à l'intérieur 1098 | 1099 | float CurrentMaxHWidth = this.UiFinalTextWidth - uiDiagSpace; //width horizontale maximal actuel 1100 | 1101 | float AccumulatedDownHeight = 0f; 1102 | 1103 | int index = 0; 1104 | while (index < this.listChild.Count) 1105 | { 1106 | TreeObject to = this.listChild[index]; 1107 | SizeWUD s = to.RecursiveComputeSize(); 1108 | 1109 | //le width continue d'augmenter 1110 | aWidth += uiDiagSpace; 1111 | CurrentMaxHWidth += uiDiagSpace; 1112 | 1113 | 1114 | //check si c'est un dossier ouvert 1115 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 1116 | { 1117 | //on ajoute le upheight et le accumulated down height 1118 | float dist = to.UpHeight + AccumulatedDownHeight; 1119 | aWidth += dist; 1120 | CurrentMaxHWidth += dist; 1121 | 1122 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 1123 | } 1124 | 1125 | //on check si l'élément est trop large pour la hauteur actuel 1126 | float diff = (2f * to.Width / R2) - to.DownHeight - CurrentMaxHWidth + uiDiagSpace; 1127 | if (diff > 0f) 1128 | { 1129 | aWidth += diff; 1130 | CurrentMaxHWidth += diff; 1131 | } 1132 | 1133 | //le down height accumulé diminue 1134 | AccumulatedDownHeight -= uiDiagSpace; 1135 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 1136 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 1137 | 1138 | 1139 | //on check le height de this 1140 | if (to.Width > height) { height = to.Width; } 1141 | 1142 | //next iteration 1143 | index++; 1144 | } 1145 | aDownHeight = this.UiFinalTextWidth * R2; 1146 | aUpHeight = height - aDownHeight; 1147 | if (aUpHeight < 0f) { aUpHeight = 0f; } 1148 | 1149 | 1150 | } 1151 | else if (this.dir == Dir.UpLeft || this.dir == Dir.DownRight) 1152 | { 1153 | aDownHeight = this.UiFinalTextWidth * R2; 1154 | aWidth = this.UiFinalTextWidth; //va augmenter en parcourant les élément enfant 1155 | aEffectiveWidth = 0f; //va augmenter en parcourant les élément enfant 1156 | 1157 | float height = 0f; //le height total correspond au plus grand width qui existe à l'intérieur 1158 | 1159 | float AccumulatedDownHeight = 0f; //le down height qui a été accumulé depuis les élément précédant 1160 | 1161 | float CurrentMaxWidth = 0f; //le width maximale qu'un dossier peut avoir à chaque instant 1162 | 1163 | int index = 0; 1164 | while (index < this.listChild.Count) 1165 | { 1166 | TreeObject to = this.listChild[index]; 1167 | SizeWUD s = to.RecursiveComputeSize(); 1168 | 1169 | //le width continue d'augmenter 1170 | aWidth += uiDiagSpace * R2; 1171 | CurrentMaxWidth += uiDiagSpace; 1172 | 1173 | 1174 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 1175 | { 1176 | //on ajoute le upheight et le accumulated down height 1177 | float dist = to.UpHeight + AccumulatedDownHeight; 1178 | aWidth += dist; 1179 | CurrentMaxWidth += dist / R2; 1180 | 1181 | //on check si le dossier a trop de width pour la hauteur actuel 1182 | float diff = (to.Width - ((to.DownHeight) / R2)) - CurrentMaxWidth; 1183 | if (diff > 0f) 1184 | { 1185 | aWidth += diff * R2; 1186 | CurrentMaxWidth += diff; 1187 | } 1188 | 1189 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 1190 | } 1191 | 1192 | //le down height accumulé diminue 1193 | AccumulatedDownHeight -= uiDiagSpace * R2; 1194 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 1195 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 1196 | 1197 | //on check pour effective width 1198 | if (aWidth / R2 + uiDiagSpace + to.UiFinalTextWidth > aEffectiveWidth) 1199 | { 1200 | aEffectiveWidth = (aWidth / R2) + uiDiagSpace + to.UiFinalTextWidth; 1201 | } 1202 | 1203 | //on check si le height total est à augmenter 1204 | if (to.Width > height) { height = to.Width; } 1205 | 1206 | //next iteration 1207 | index++; 1208 | } 1209 | 1210 | //le upheight est le height restant, qui n'est pas pris par downheight 1211 | aUpHeight = height - aDownHeight; 1212 | if (aUpHeight < 0f) { aUpHeight = 0f; } 1213 | 1214 | 1215 | 1216 | } 1217 | else if (this.dir == Dir.DownLeft || this.dir == Dir.UpRight) 1218 | { 1219 | 1220 | //le height total est égale au plus grand width à l'intérieur, et est au minimum la largeur du text du dossier 1221 | float height = this.UiFinalTextWidth; //le height de départ. si qqc à l'intérieur a un width plus grand, alors on va donner ce width à cette variable 1222 | 1223 | aWidth = this.UiFinalTextWidth * R2; 1224 | 1225 | float CurrentMaxEffectiveWidth = this.UiFinalTextWidth - uiDiagSpace; 1226 | 1227 | int index = 0; 1228 | while (index < this.listChild.Count) 1229 | { 1230 | TreeObject to = this.listChild[index]; 1231 | SizeWUD s = to.RecursiveComputeSize(); 1232 | CurrentMaxEffectiveWidth += uiDiagSpace; 1233 | 1234 | //on check le height total de this 1235 | if (s.Width + uiDiagSpace > height) { height = s.Width + uiDiagSpace; } 1236 | 1237 | ////on check si l'item est trop large pour la hauteur actuel 1238 | //if (to.Width > CurrentMaxWidth) 1239 | //{ 1240 | // aWidth += (to.Width - CurrentMaxWidth) * R2; 1241 | // CurrentMaxWidth = to.Width; 1242 | //} 1243 | 1244 | //on accumule sur la variable width 1245 | aWidth += uiDiagSpace * R2; 1246 | //si c'est un dossier ouvert, il faut ajouter à width le upheight du dossier 1247 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 1248 | { 1249 | aWidth += to.UpHeight; 1250 | CurrentMaxEffectiveWidth += to.UpHeight / R2; 1251 | } 1252 | 1253 | //on check si le effective width de l'élément est trop grand pour la hauteur actuel 1254 | if (to.EffectiveWidth > CurrentMaxEffectiveWidth) 1255 | { 1256 | float diff = to.EffectiveWidth - CurrentMaxEffectiveWidth; 1257 | aWidth += diff * R2; 1258 | CurrentMaxEffectiveWidth += diff; 1259 | } 1260 | 1261 | 1262 | //next iteration 1263 | index++; 1264 | } 1265 | 1266 | //on calcul upheight et height à partir de height 1267 | aDownHeight = this.UiFinalTextWidth; 1268 | aUpHeight = height - aDownHeight; 1269 | 1270 | 1271 | } 1272 | else if (this.dir == Dir.Right || this.dir == Dir.Left) 1273 | { 1274 | aDownHeight = 0f; 1275 | aUpHeight = 0f; //this.UiFinalTextWidth / R2; //sera augmenté lorsqu'on trouvera des enfant ayant un width plus grand que notre upheight 1276 | 1277 | aWidth = this.UiFinalTextWidth; //sera augmenté au fur et à mesure qu'on avance dans les enfant 1278 | aEffectiveWidth = this.UiFinalTextWidth; 1279 | 1280 | //le downheight accumulé précédement. lorsqu'un dossier est ouvert, il doit se déplacer de cette distance pour ne pas chevaucher les dossier précédant 1281 | float AccumulatedDownHeight = 0f; 1282 | 1283 | //hauteur graphique maximal actuel 1284 | float CurrentMaxVWidth = this.UiFinalTextWidth - (uiDiagSpace * R2); 1285 | 1286 | int index = 0; 1287 | while (index < this.listChild.Count) 1288 | { 1289 | TreeObject to = this.listChild[index]; 1290 | SizeWUD s = to.RecursiveComputeSize(); 1291 | 1292 | 1293 | //on avance horizontalement 1294 | aWidth += uiDiagSpace * R2; 1295 | CurrentMaxVWidth += uiDiagSpace * R2; 1296 | 1297 | //on check si c'est un dossier ouvert et non vide 1298 | if (to.IsFolder && to.IsOpen && to.NotEmpty) 1299 | { 1300 | //on doit ajouter le upheight et le accumulated down height 1301 | float dist = to.UpHeight + AccumulatedDownHeight; 1302 | aWidth += dist; 1303 | CurrentMaxVWidth += dist; 1304 | 1305 | //on check si le dossier a trop de width verticale pour la position horizontale actuel 1306 | float diff = (to.Width / R2) - to.DownHeight + to.EffectiveWidth - CurrentMaxVWidth; //(2f * to.Width / R2) + to.UpHeight - CurrentMaxVWidth; 1307 | if (diff > 0f) 1308 | { 1309 | aWidth += diff; 1310 | CurrentMaxVWidth += diff; 1311 | } 1312 | 1313 | AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); 1314 | } 1315 | 1316 | //on vérifie effective width 1317 | if (aWidth + uiDiagSpace + (to.UiFinalTextWidth / R2) > aEffectiveWidth) 1318 | { 1319 | aEffectiveWidth = aWidth + uiDiagSpace + (to.UiFinalTextWidth / R2); 1320 | } 1321 | 1322 | 1323 | //le down height accumulé diminue 1324 | AccumulatedDownHeight -= uiDiagSpace * R2; 1325 | if (AccumulatedDownHeight < 0f) { AccumulatedDownHeight = 0f; } 1326 | if (AccumulatedDownHeight < to.DownHeight + (uiDiagSpace * R2)) { AccumulatedDownHeight = to.DownHeight + (uiDiagSpace * R2); } 1327 | 1328 | 1329 | //on check pour le upheight de this 1330 | if ((uiDiagSpace * R2) + to.Width > aUpHeight) { aUpHeight = (uiDiagSpace * R2) + to.Width; } 1331 | 1332 | 1333 | //next iteration 1334 | index++; 1335 | } 1336 | 1337 | 1338 | } 1339 | } 1340 | else //this est un fichier ou un dossier fermé 1341 | { 1342 | //les dossier fermé, ou ouvert et vide, et les fichier sont comparable donc on les traite de la même facon 1343 | if (this.dir == Dir.Up || this.dir == Dir.Down) 1344 | { 1345 | aUpHeight = 0f; 1346 | aDownHeight = this.UiFinalTextWidth * R2; 1347 | aWidth = this.UiFinalTextWidth; 1348 | } 1349 | else if (this.dir == Dir.UpLeft || this.dir == Dir.DownRight) 1350 | { 1351 | aUpHeight = 0f; 1352 | aWidth = this.UiFinalTextWidth; 1353 | aDownHeight = this.UiFinalTextWidth * R2; 1354 | } 1355 | else if (this.dir == Dir.DownLeft || this.dir == Dir.UpRight) 1356 | { 1357 | aWidth = this.UiFinalTextWidth * R2; 1358 | aUpHeight = 0f; 1359 | aDownHeight = this.UiFinalTextWidth; 1360 | } 1361 | else if (this.dir == Dir.Right || this.dir == Dir.Left) 1362 | { 1363 | aWidth = this.UiFinalTextWidth; 1364 | aUpHeight = 0f; //aWidth / R2; 1365 | aDownHeight = 0f; 1366 | aEffectiveWidth = this.UiFinalTextWidth; 1367 | } 1368 | } 1369 | 1370 | 1371 | this.Width = aWidth; 1372 | this.UpHeight = aUpHeight; 1373 | this.DownHeight = aDownHeight; 1374 | this.EffectiveWidth = aEffectiveWidth; 1375 | return new SizeWUD(aWidth, aUpHeight, aDownHeight); 1376 | //return sd.Add(this, Width, UpHeight, DownHeight); 1377 | } 1378 | 1379 | 1380 | 1381 | //ce n'est pas la bonne facon d'implémenter un hash code mais je m'en fou pour autant que ca marche bien, ce qui est le cas ici 1382 | private int zzzHashCode = TreeView8.GetNextHashCode(); 1383 | public override int GetHashCode() 1384 | { 1385 | return this.zzzHashCode; 1386 | } 1387 | } 1388 | private static int NextHashCode = 0; 1389 | private static int GetNextHashCode() 1390 | { 1391 | NextHashCode++; 1392 | return NextHashCode; 1393 | } 1394 | 1395 | 1396 | 1397 | 1398 | private struct SizeWUD 1399 | { 1400 | public float Width; 1401 | public float UpHeight; 1402 | public float DownHeight; 1403 | public SizeWUD(float sWidth, float sUpHeight, float sDownHeight) 1404 | { 1405 | this.Width = sWidth; 1406 | this.UpHeight = sUpHeight; 1407 | this.DownHeight = sDownHeight; 1408 | } 1409 | } 1410 | 1411 | //private class SDictionnary 1412 | //{ 1413 | // public Dictionary dict = new Dictionary(); 1414 | // public SizeWUD Add(TreeObject to, float Width, float UpHeight, float DownHeight) 1415 | // { 1416 | // SizeWUD s = new SizeWUD(Width, UpHeight, DownHeight); 1417 | // this.dict.Add(to, s); 1418 | // return s; 1419 | // } 1420 | // public SizeWUD GetSize(TreeObject to) 1421 | // { 1422 | // return this.dict[to]; 1423 | // } 1424 | // public SDictionnary() 1425 | // { 1426 | // } 1427 | //} 1428 | 1429 | 1430 | 1431 | 1432 | 1433 | private void BuildTreeRoot() 1434 | { 1435 | TreeObject r = new TreeObject("This PC", this); 1436 | r.tType = TOType.folder; 1437 | r.dir = Dir.Up; // Up 1438 | 1439 | //TreeObject toC = new TreeObject(r, "C:\\", TOType.folder, this); 1440 | //toC.UiName = "C:\\"; 1441 | //TreeObject toD = new TreeObject(r, "D:\\", TOType.folder, this); 1442 | //toD.UiName = "D:\\"; 1443 | 1444 | 1445 | string[] arDrives = System.IO.Directory.GetLogicalDrives(); 1446 | foreach (string l in arDrives) 1447 | { 1448 | TreeObject to = new TreeObject(r, l, TOType.folder, this); 1449 | to.UiName = l; 1450 | } 1451 | 1452 | 1453 | 1454 | //////TESTEST 1455 | //TreeObject newtest = new TreeObject(r, "test", TOType.folder, this); 1456 | //newtest.UiName = "test asdhfiuahrguiaheugiauerhg"; 1457 | //newtest.ChildLoaded = true; 1458 | //for (int i = 1; i <= 10; i++) 1459 | //{ 1460 | // string name = "f" + i.ToString(); 1461 | // TreeObject newto = new TreeObject(newtest, "test:\\" + name, TOType.file, this); 1462 | // newto.UiName = name; 1463 | //} 1464 | //////END TESTEST 1465 | 1466 | 1467 | 1468 | r.ChildLoaded = true; 1469 | 1470 | this.Root = r; 1471 | this.ActualElement = r; 1472 | } 1473 | 1474 | #endregion 1475 | #region navigation 1476 | 1477 | //à caller depuis l'extérieur, par celui qui recoit les touche 1478 | public void KeyDown(KeyEventArgs e) 1479 | { 1480 | 1481 | //navigation verticale dans le dossier 1482 | if (e.KeyCode == Keys.Up) 1483 | { 1484 | ////on remonte dans le dossier 1485 | //on check si l'objet actuel a un parent 1486 | if (this.ActualElement.Parent != null) 1487 | { 1488 | //nous recherchons l'index de l'élément actuel 1489 | int ActualIndex = this.ActualElement.Parent.listChild.IndexOf(this.ActualElement); 1490 | //on remonte de 1 1491 | ActualIndex--; 1492 | //si le nouveau index est négatif, alors on remonte au parent 1493 | if (ActualIndex < 0) 1494 | { 1495 | this.ActualElement = this.ActualElement.Parent; 1496 | } 1497 | else 1498 | { 1499 | //si le nouveau index n'est pas négatif, il change pour l'item du nouveau index 1500 | this.ActualElement = this.ActualElement.Parent.listChild[ActualIndex]; 1501 | } 1502 | 1503 | } 1504 | else 1505 | { 1506 | //si l'objet actuel n'a pas de parent, alors nous somme à la racine et il n'y a rien à faire 1507 | 1508 | } 1509 | 1510 | } 1511 | if (e.KeyCode == Keys.Down) 1512 | { 1513 | ////on dessant dans le dossier 1514 | //on check si l'objet actuel a un parent 1515 | if (this.ActualElement.Parent != null) 1516 | { 1517 | //on obtien l'index de l'élément actuel 1518 | int ActualIndex = this.ActualElement.Parent.listChild.IndexOf(this.ActualElement); 1519 | //on augmente l'index pour passer à l'élément suivant 1520 | ActualIndex++; 1521 | //on make sure que l'index existe 1522 | if (ActualIndex < this.ActualElement.Parent.listChild.Count) 1523 | { 1524 | //on set l'élément suivant comme le nouveau élément actuel 1525 | this.ActualElement = this.ActualElement.Parent.listChild[ActualIndex]; 1526 | } 1527 | 1528 | } 1529 | else 1530 | { 1531 | ////si l'object actuel n'a pas de parent, ca veut dire qu'on est à la racine. on change donc pour le premier enfant 1532 | //make sure que la racine (considéré comme un dossier) est ouverte 1533 | if (!this.ActualElement.IsOpen) { this.ActualElement.Open(); } 1534 | //on set l'élément actuel en tant que le premier élément enfant 1535 | //met on check d'abord s'il y a des enfant à l'intérieur du dossier 1536 | if (this.ActualElement.listChild.Count > 0) 1537 | { 1538 | this.ActualElement = this.ActualElement.listChild[0]; 1539 | } 1540 | } 1541 | 1542 | } 1543 | 1544 | //touche enter qui fait ouvrire ou fermer un dossier 1545 | if (e.KeyCode == Keys.Return) 1546 | { 1547 | //check si c'est un dossier 1548 | if (this.ActualElement.IsFolder) 1549 | { 1550 | //si c'est fermé on l'ouvre ou l'inverse 1551 | if (this.ActualElement.IsOpen) 1552 | { 1553 | this.ActualElement.Close(); 1554 | this.Root.RecursiveComputeSize(); 1555 | } 1556 | else 1557 | { 1558 | this.ActualElement.Open(); 1559 | this.Root.RecursiveComputeSize(); 1560 | } 1561 | } 1562 | } 1563 | 1564 | //touche gauche droite qui fait sortir ou entrer dans un dossier 1565 | if (e.KeyCode == Keys.Right) 1566 | { 1567 | //si c'est un dossier, on entre à l'intérieur 1568 | if (this.ActualElement.IsFolder) 1569 | { 1570 | //make sure que le dossier est ouvert 1571 | if (!this.ActualElement.IsOpen) 1572 | { 1573 | this.ActualElement.Open(); 1574 | } 1575 | //si le dossier a des enfant, on défini l'élément actuel comme son premier enfant 1576 | if (this.ActualElement.listChild.Count > 0) 1577 | { 1578 | this.ActualElement = this.ActualElement.listChild[0]; 1579 | } 1580 | 1581 | } 1582 | } 1583 | if (e.KeyCode == Keys.Left) 1584 | { 1585 | //on remonte au parent, s'il y en a un 1586 | if (this.ActualElement.Parent != null) 1587 | { 1588 | this.ActualElement = this.ActualElement.Parent; 1589 | } 1590 | } 1591 | 1592 | //reset la position actuel 1593 | if (e.KeyCode == Keys.Space) 1594 | { 1595 | this.RootDrawPos = new Point(0, 0); 1596 | } 1597 | 1598 | this.RefreshImage(); 1599 | } 1600 | 1601 | 1602 | 1603 | private void ImageBox_MouseDown(object sender, MouseEventArgs e) 1604 | { 1605 | if (e.Button == MouseButtons.Left) 1606 | { 1607 | //on make sure que l'user n'a pas clické sur un button 1608 | if (!this.IsMouseOnAnyControl()) 1609 | { 1610 | //on récupère l'object qui se trouve sous la souris 1611 | TreeObject to = this.GetObjectUnderMouse(); 1612 | if (to != null) 1613 | { 1614 | //on défini cet élément comme l'élément "actuel" ou ayant le focus 1615 | this.ActualElement = to; 1616 | 1617 | //si c'est un fichier, on fait rien. si c'est un dossier, on l'ouvre ou on le ferme 1618 | if (to.IsFolder) 1619 | { 1620 | //on conserve sa position graphique 1621 | int toLastX = to.uiLastX; 1622 | int toLastY = to.uiLastY; 1623 | 1624 | //on ouvre ou on ferme le dossier 1625 | if (to.IsOpen) { to.Close(); } 1626 | else { to.Open(); } 1627 | 1628 | //maintenant on leur fait refresher leur coordonné graphique 1629 | int imgWidth = this.Width; 1630 | int imgHeight = this.Height; 1631 | if (imgWidth < 50) { imgWidth = 50; } 1632 | if (imgHeight < 50) { imgHeight = 50; } 1633 | Point ppMiddle = new Point(imgWidth / 2, imgHeight / 2); 1634 | Bitmap asdfimg = new Bitmap(10, 10); 1635 | Graphics g = Graphics.FromImage(asdfimg); 1636 | this.Root.RecursiveComputeSize(); 1637 | this.Root.DrawFullAt(ppMiddle.X + this.RootDrawPos.X, ppMiddle.Y + this.RootDrawPos.Y, g); 1638 | g.Dispose(); 1639 | asdfimg.Dispose(); 1640 | 1641 | 1642 | 1643 | //on réajuste la position graphique pour le dossier revient au même endroit où il était 1644 | this.RootDrawPos.X -= to.uiLastX - toLastX; 1645 | this.RootDrawPos.Y -= to.uiLastY - toLastY; 1646 | 1647 | 1648 | } 1649 | this.RefreshImage(); 1650 | } 1651 | else //si la fonction a retourné null, alors il n'y a pas d'objet sous la souris et il faut plutot commencer un scroll 1652 | { 1653 | this.StartScroll(); 1654 | 1655 | } 1656 | } 1657 | else //l'user a clické sur un button 1658 | { 1659 | this.Control_MouseLeftDown(); 1660 | } 1661 | } 1662 | } 1663 | private void ImageBox_MouseUp(object sender, MouseEventArgs e) 1664 | { 1665 | if (e.Button == MouseButtons.Left) 1666 | { 1667 | //si l'user est en train de scroller on arrête le scroll 1668 | if (this.IsScrolling) 1669 | { 1670 | this.StopScroll(); 1671 | } 1672 | 1673 | 1674 | } 1675 | } 1676 | private void ImageBox_MouseMove(object sender, MouseEventArgs e) 1677 | { 1678 | //if (this.IsScrolling) 1679 | //{ 1680 | // this.ReshowNetralScroll(); 1681 | //} 1682 | } 1683 | private void ImageBox_DoubleMouseClick(object sender, MouseEventArgs e) 1684 | { 1685 | if (e.Button == MouseButtons.Left) 1686 | { 1687 | if (!this.IsMouseOnAnyControl()) 1688 | { 1689 | //on récupère l'object qui se trouve sous la souris 1690 | TreeObject to = this.GetObjectUnderMouse(); 1691 | if (to != null) 1692 | { 1693 | //on make sure que c'est un fichier 1694 | if (to.tType == TOType.file) 1695 | { 1696 | try 1697 | { 1698 | //on run le fichier 1699 | System.Diagnostics.Process.Start(to.Path); 1700 | 1701 | } 1702 | catch { } 1703 | } 1704 | } 1705 | } 1706 | } 1707 | } 1708 | 1709 | 1710 | 1711 | //retourne le TreeObject graphiquement actuellement sous la souris. retourne null s'il n'y en a aucun 1712 | private TreeObject GetObjectUnderMouse() 1713 | { 1714 | return this.Root.GetObjetUnderPos(this.MousePos.X, this.MousePos.Y); 1715 | } 1716 | 1717 | 1718 | 1719 | 1720 | private Timer ScrollTimer = null; 1721 | private void CreateScroll() 1722 | { 1723 | this.ScrollTimer = new Timer(); 1724 | this.ScrollTimer.Interval = 100; // 250 1725 | this.ScrollTimer.Tick += new EventHandler(this.ScrollTimer_Tick); 1726 | 1727 | } 1728 | private void ScrollTimer_Tick(object sender, EventArgs e) 1729 | { 1730 | Point mpos = this.MousePos; 1731 | //calcul le déplacement de la souris 1732 | int dx = mpos.X - this.ScrollStartPos.X; 1733 | int dy = mpos.Y - this.ScrollStartPos.Y; 1734 | 1735 | //applique le déplacement 1736 | this.RootDrawPos.X -= dx; 1737 | this.RootDrawPos.Y -= dy; 1738 | this.RefreshImage(); 1739 | 1740 | //redessine la position neutre du scrolling 1741 | this.ReshowNetralScroll(); 1742 | 1743 | } 1744 | 1745 | private bool IsScrolling = false; //indique si l'user est en train de scroller 1746 | private Point ScrollStartPos = new Point(0, 0); //point de départ du scrolling 1747 | private void StartScroll() 1748 | { 1749 | this.IsScrolling = true; 1750 | this.ScrollStartPos = this.MousePos; 1751 | this.ScrollTimer.Start(); 1752 | } 1753 | private void StopScroll() 1754 | { 1755 | this.IsScrolling = false; 1756 | this.ScrollTimer.Stop(); 1757 | this.ImageBox.Refresh(); 1758 | } 1759 | 1760 | 1761 | //réaffiche graphiquement la position neutre du scrolling avec la flèche qui pointe vers la souris 1762 | private void ReshowNetralScroll() 1763 | { 1764 | this.ImageBox.Refresh(); 1765 | ////redessine la position neutre du scrolling 1766 | Graphics g = this.ImageBox.CreateGraphics(); 1767 | //g.FillRectangle(Brushes.White, this.ScrollStartPos.X - 3, this.ScrollStartPos.Y - 3, 6, 6); 1768 | 1769 | Point mpos = this.MousePos; 1770 | //obtien le décalage de la position de la souris relativement à la position neutre du scrolling 1771 | int dx = mpos.X - this.ScrollStartPos.X; 1772 | int dy = mpos.Y - this.ScrollStartPos.Y; 1773 | 1774 | //obtien l'angle de la souris 1775 | double radAngle = 0d; 1776 | if (dx != 0d || dy != 0d) 1777 | { 1778 | radAngle = Math.Atan2((double)dy, (double)dx); 1779 | } 1780 | 1781 | //converti en degré 1782 | double degAngle = radAngle / Math.PI * 180d; 1783 | 1784 | //obtien et dessine l'image de la flèche 1785 | Bitmap img = this.GetArrowAtAngle(degAngle); 1786 | g.DrawImage(img, this.ScrollStartPos.X - (img.Width / 2), this.ScrollStartPos.Y - (img.Height / 2)); 1787 | img.Dispose(); 1788 | 1789 | g.Dispose(); 1790 | } 1791 | 1792 | 1793 | 1794 | 1795 | private Bitmap imgArrow; 1796 | private void CreateArrow() 1797 | { 1798 | int imgWidth = 50; // 30 1799 | Bitmap img = new Bitmap(imgWidth, imgWidth); 1800 | Graphics g = Graphics.FromImage(img); 1801 | //g.Clear(Color.DimGray); 1802 | g.Clear(Color.Transparent); 1803 | 1804 | 1805 | int w = imgWidth; 1806 | int wd2 = imgWidth / 2; 1807 | int wd4 = (int)((double)(imgWidth) / 3.5d); 1808 | int wd6 = imgWidth / 6; 1809 | int wd8 = imgWidth / 8; 1810 | 1811 | Point[] plArrow = new Point[] { new Point(w - 1, wd2), new Point(wd2 - 1, 0), new Point(wd2 - 1, wd4), new Point(0, wd4), new Point(0, w - wd4), new Point(wd2 - 1, w - wd4), new Point(wd2 - 1, w) }; 1812 | 1813 | Point[] plArrowUp = new Point[] { new Point(w - 1, wd2), new Point(wd2 - 1, 0), new Point(wd2 - 1, wd4), new Point(0, wd4), new Point(0, wd2) }; 1814 | Point[] plArrowDown = new Point[] { new Point(w - 1, wd2), new Point(0, wd2), new Point(0, w - wd4), new Point(wd2 - 1, w - wd4), new Point(wd2 - 1, w) }; 1815 | Point[] plArrowMiddle = new Point[] { new Point(w - 1, wd2), new Point(wd2 + 1, wd6), new Point(wd2 + 1, wd4 + wd8), new Point(0, wd4 + wd8), new Point(0, w - wd4 - wd8), new Point(wd2 + 1, w - wd4 - wd8), new Point(wd2 + 1, w - wd6) }; 1816 | 1817 | g.FillPolygon(Brushes.MediumAquamarine, plArrowUp); 1818 | g.FillPolygon(Brushes.SeaGreen, plArrowDown); 1819 | g.FillPolygon(Brushes.MediumSeaGreen, plArrowMiddle); 1820 | g.DrawPolygon(Pens.White, plArrow); 1821 | 1822 | g.Dispose(); 1823 | this.imgArrow = img; 1824 | } 1825 | 1826 | 1827 | private Bitmap GetArrowAtAngle(double angle) 1828 | { 1829 | int imgWidth = (int)((double)(this.imgArrow.Width) * 1.4142d); 1830 | Bitmap img = new Bitmap(imgWidth, imgWidth); 1831 | Graphics g = Graphics.FromImage(img); 1832 | g.Clear(Color.Transparent); 1833 | 1834 | g.TranslateTransform((float)imgWidth / 2f, (float)imgWidth / 2f); 1835 | g.RotateTransform((float)angle); 1836 | g.TranslateTransform((float)imgWidth / -2f, (float)imgWidth / -2f); 1837 | 1838 | int delta = (imgWidth / 2) - (this.imgArrow.Width / 2); 1839 | g.DrawImage(this.imgArrow, delta, delta); 1840 | 1841 | 1842 | g.Dispose(); 1843 | return img; 1844 | } 1845 | 1846 | 1847 | 1848 | #endregion 1849 | 1850 | 1851 | public TreeView8() 1852 | { 1853 | this.ImageBox = new PictureBox(); 1854 | this.ImageBox.BackColor = Color.Black; 1855 | this.ImageBox.SizeChanged += new EventHandler(this.ImageBox_SizeChanged); 1856 | this.ImageBox.MouseDown += new MouseEventHandler(this.ImageBox_MouseDown); 1857 | this.ImageBox.MouseUp += new MouseEventHandler(this.ImageBox_MouseUp); 1858 | this.ImageBox.MouseMove += new MouseEventHandler(this.ImageBox_MouseMove); 1859 | this.ImageBox.MouseDoubleClick += new MouseEventHandler(this.ImageBox_DoubleMouseClick); 1860 | 1861 | this.CreateScroll(); 1862 | this.CreateArrow(); 1863 | this.CreateInterface(); 1864 | 1865 | this.BuildTreeRoot(); 1866 | } 1867 | private void ImageBox_SizeChanged(object sender, EventArgs e) 1868 | { 1869 | this.RefreshImage(); 1870 | } 1871 | 1872 | 1873 | 1874 | #region graphique 1875 | 1876 | //juste pour le measure string 1877 | private Graphics zzzggg = Graphics.FromImage(new Bitmap(10, 10)); 1878 | public SizeF MeasureString(string text, Font font) 1879 | { 1880 | return this.zzzggg.MeasureString(text, font); 1881 | } 1882 | 1883 | 1884 | 1885 | private float Sqrt2 = (float)(Math.Sqrt(2d)); 1886 | 1887 | 1888 | private Font uiItemFont = new Font("consolas", 10f); // 10f 1889 | private float uifItemTextHeight = 20f; // 8f ajusté lors de RefreshImage() 1890 | 1891 | private Pen uiHighlightPen = Pens.White; //pen utilisé pour dessiner un rectangle qui indique quel est l'élément actuellement focusé, ou juste "actuel" 1892 | private Brush uiFolderBackBrush = new SolidBrush(Color.FromArgb(96, 96, 0)); //brush utilisé pour filler l'arrière plan du nom d'un dossier 1893 | 1894 | private float uiDiagSpace = 18; // 18 distance horizontale et vertical à parcourir pour passer immédiatement au prochain enfant en diagonalde 1895 | 1896 | 1897 | 1898 | private Point RootDrawPos = new Point(0, 0); //position graphique, relative au milieu de l'écran, à laquelle dessiner la root 1899 | 1900 | 1901 | public void RefreshImage() 1902 | { 1903 | int imgWidth = this.Width; 1904 | int imgHeight = this.Height; 1905 | if (imgWidth < 50) { imgWidth = 50; } 1906 | if (imgHeight < 50) { imgHeight = 50; } 1907 | Bitmap img = new Bitmap(imgWidth, imgHeight); 1908 | Graphics g = Graphics.FromImage(img); 1909 | g.Clear(Color.FromArgb(16, 16, 16)); 1910 | 1911 | Point ppMiddle = new Point(imgWidth / 2, imgHeight / 2); 1912 | PointF fppMiddle = new PointF((float)imgWidth / 2f, (float)imgHeight / 2f); 1913 | 1914 | //obtien la hauteur du text d'un item 1915 | float fItemTextHeight = this.MeasureString("asdfgathSTHS", this.uiItemFont).Height; 1916 | this.uifItemTextHeight = fItemTextHeight; 1917 | 1918 | 1919 | 1920 | //this.Root.RecursiveComputeSize(); 1921 | 1922 | 1923 | 1924 | this.Root.DrawFullAt(ppMiddle.X + this.RootDrawPos.X, ppMiddle.Y + this.RootDrawPos.Y, g); 1925 | 1926 | 1927 | 1928 | 1929 | ////testest 1930 | //int asdf = 5000; 1931 | //g.DrawLine(Pens.Lime, ppMiddle.X + this.RootDrawPos.X - this.uiDiagSpace, ppMiddle.Y + this.RootDrawPos.Y, ppMiddle.X + this.RootDrawPos.X - asdf - this.uiDiagSpace, ppMiddle.Y + this.RootDrawPos.Y - asdf); 1932 | 1933 | 1934 | 1935 | //on dessine les button 1936 | foreach (uiButton btn in this.listButton) 1937 | { 1938 | g.FillRectangle(Brushes.DimGray, btn.rec); 1939 | g.DrawRectangle(Pens.Silver, btn.rec); 1940 | 1941 | SizeF TextSizeF = g.MeasureString(btn.Text, btn.Font); 1942 | g.DrawString(btn.Text, btn.Font, Brushes.White, (float)(btn.Left) + (btn.Width / 2f) - (TextSizeF.Width / 2f), (float)(btn.Top) + (btn.Height / 2f) - (TextSizeF.Height / 2f)); 1943 | 1944 | 1945 | } 1946 | 1947 | 1948 | 1949 | //////fin 1950 | g.Dispose(); 1951 | if (this.ImageBox.Image != null) { this.ImageBox.Image.Dispose(); } 1952 | this.ImageBox.Image = img; 1953 | this.ImageBox.Refresh(); 1954 | } 1955 | 1956 | 1957 | #endregion 1958 | #region interface 1959 | 1960 | 1961 | 1962 | private void CreateInterface() 1963 | { 1964 | uiButton btnCloseAll = new uiButton(this); 1965 | btnCloseAll.SetSize(100, 30); 1966 | btnCloseAll.SetPos(5, 5); 1967 | btnCloseAll.Text = "Close all"; 1968 | btnCloseAll.MouseLeftDown += new EventHandler((o, e) => 1969 | { 1970 | this.Root.RecursiveCloseAll(); 1971 | this.RootDrawPos = new Point(0, 0); 1972 | this.ActualElement = this.Root; 1973 | this.RefreshImage(); 1974 | }); 1975 | 1976 | uiButton btnExpandAll = new uiButton(this); 1977 | btnExpandAll.SetSize(120, 30); 1978 | btnExpandAll.SetPos(btnCloseAll.Left + btnCloseAll.Width + 5, 5); 1979 | btnExpandAll.Text = "Expand all"; 1980 | btnExpandAll.MouseLeftDown += new EventHandler((o, e) => 1981 | { 1982 | this.Root.RecursiveOpenAll(); 1983 | this.RootDrawPos = new Point(0, 0); 1984 | this.ActualElement = this.Root; 1985 | this.RefreshImage(); 1986 | }); 1987 | 1988 | 1989 | } 1990 | #endregion 1991 | 1992 | 1993 | private List listButton = new List(); 1994 | 1995 | private bool IsMouseOnAnyControl() 1996 | { 1997 | Rectangle mrec = this.MouseRec; 1998 | foreach (uiButton btn in this.listButton) 1999 | { 2000 | if (btn.rec.IntersectsWith(mrec)) 2001 | { 2002 | return true; 2003 | } 2004 | } 2005 | return false; 2006 | } 2007 | private void Control_MouseLeftDown() 2008 | { 2009 | Rectangle mrec = this.MouseRec; 2010 | foreach (uiButton btn in this.listButton) 2011 | { 2012 | if (btn.rec.IntersectsWith(mrec)) 2013 | { 2014 | btn.Raise_MouseLeftDown(); 2015 | } 2016 | } 2017 | } 2018 | 2019 | 2020 | private class uiButton 2021 | { 2022 | private TreeView8 zzzParent; 2023 | 2024 | public Rectangle rec = new Rectangle(0, 0, 50, 30); 2025 | public void SetPos(int newLeft, int newTop) 2026 | { 2027 | this.Left = newLeft; 2028 | this.Top = newTop; 2029 | } 2030 | public int Top 2031 | { 2032 | get { return this.rec.Y; } 2033 | set { this.rec.Y = value; } 2034 | } 2035 | public int Left 2036 | { 2037 | get { return this.rec.X; } 2038 | set { this.rec.X = value; } 2039 | } 2040 | public void SetSize(int newWidth, int newHeight) 2041 | { 2042 | this.Width = newWidth; 2043 | this.Height = newHeight; 2044 | } 2045 | public int Width 2046 | { 2047 | get { return this.rec.Width; } 2048 | set { this.rec.Width = value; } 2049 | } 2050 | public int Height 2051 | { 2052 | get { return this.rec.Height; } 2053 | set { this.rec.Height = value; } 2054 | } 2055 | public string Text = "notext"; 2056 | public Font Font = new Font("consolas", 10f); 2057 | 2058 | public uiButton(TreeView8 sParent) 2059 | { 2060 | this.zzzParent = sParent; 2061 | sParent.listButton.Add(this); 2062 | } 2063 | 2064 | public event EventHandler MouseLeftDown; 2065 | public void Raise_MouseLeftDown() 2066 | { 2067 | if (this.MouseLeftDown != null) 2068 | { 2069 | this.MouseLeftDown(this, new EventArgs()); 2070 | } 2071 | } 2072 | 2073 | 2074 | 2075 | } 2076 | 2077 | } 2078 | } 2079 | --------------------------------------------------------------------------------