-
Notifications
You must be signed in to change notification settings - Fork 8.4k
/
viewport.cpp
1081 lines (994 loc) · 44.5 KB
/
viewport.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/Viewport.hpp"
using namespace Microsoft::Console::Types;
Viewport::Viewport(const SMALL_RECT sr) noexcept :
_sr(sr)
{
}
Viewport::Viewport(const Viewport& other) noexcept :
_sr(other._sr)
{
}
Viewport Viewport::Empty() noexcept
{
return Viewport();
}
Viewport Viewport::FromInclusive(const SMALL_RECT sr) noexcept
{
return Viewport(sr);
}
Viewport Viewport::FromExclusive(const SMALL_RECT sr) noexcept
{
SMALL_RECT _sr = sr;
_sr.Bottom -= 1;
_sr.Right -= 1;
return Viewport::FromInclusive(_sr);
}
// Function Description:
// - Creates a new Viewport at the given origin, with the given dimensions.
// Arguments:
// - origin: The origin of the new Viewport. Becomes the Viewport's Left, Top
// - width: The width of the new viewport
// - height: The height of the new viewport
// Return Value:
// - a new Viewport at the given origin, with the given dimensions.
Viewport Viewport::FromDimensions(const COORD origin,
const short width,
const short height) noexcept
{
return Viewport::FromExclusive({ origin.X, origin.Y, origin.X + width, origin.Y + height });
}
// Function Description:
// - Creates a new Viewport at the given origin, with the given dimensions.
// Arguments:
// - origin: The origin of the new Viewport. Becomes the Viewport's Left, Top
// - dimensions: A coordinate containing the width and height of the new viewport
// in the x and y coordinates respectively.
// Return Value:
// - a new Viewport at the given origin, with the given dimensions.
Viewport Viewport::FromDimensions(const COORD origin,
const COORD dimensions) noexcept
{
return Viewport::FromExclusive({ origin.X, origin.Y, origin.X + dimensions.X, origin.Y + dimensions.Y });
}
// Function Description:
// - Creates a new Viewport at the origin, with the given dimensions.
// Arguments:
// - dimensions: A coordinate containing the width and height of the new viewport
// in the x and y coordinates respectively.
// Return Value:
// - a new Viewport at the origin, with the given dimensions.
Viewport Viewport::FromDimensions(const COORD dimensions) noexcept
{
return Viewport::FromDimensions({ 0 }, dimensions);
}
// Method Description:
// - Creates a Viewport equivalent to a 1x1 rectangle at the given coordinate.
// Arguments:
// - origin: origin of the rectangle to create.
// Return Value:
// - a 1x1 Viewport at the given coordinate
Viewport Viewport::FromCoord(const COORD origin) noexcept
{
return Viewport::FromInclusive({ origin.X, origin.Y, origin.X, origin.Y });
}
SHORT Viewport::Left() const noexcept
{
return _sr.Left;
}
SHORT Viewport::RightInclusive() const noexcept
{
return _sr.Right;
}
SHORT Viewport::RightExclusive() const noexcept
{
return _sr.Right + 1;
}
SHORT Viewport::Top() const noexcept
{
return _sr.Top;
}
SHORT Viewport::BottomInclusive() const noexcept
{
return _sr.Bottom;
}
SHORT Viewport::BottomExclusive() const noexcept
{
return _sr.Bottom + 1;
}
SHORT Viewport::Height() const noexcept
{
return BottomExclusive() - Top();
}
SHORT Viewport::Width() const noexcept
{
return RightExclusive() - Left();
}
// Method Description:
// - Get a coord representing the origin of this viewport.
// Arguments:
// - <none>
// Return Value:
// - the coordinates of this viewport's origin.
COORD Viewport::Origin() const noexcept
{
return { Left(), Top() };
}
// Method Description:
// - For Accessibility, get a COORD representing the end of this viewport in exclusive terms.
// - This is needed to represent an exclusive endpoint in UiaTextRange that includes the last
// COORD's text in the buffer at (RightInclusive(), BottomInclusive())
// Arguments:
// - <none>
// Return Value:
// - the coordinates of this viewport's end.
COORD Viewport::EndExclusive() const noexcept
{
return { Left(), BottomExclusive() };
}
// Method Description:
// - Get a coord representing the dimensions of this viewport.
// Arguments:
// - <none>
// Return Value:
// - the dimensions of this viewport.
COORD Viewport::Dimensions() const noexcept
{
return { Width(), Height() };
}
// Method Description:
// - Determines if the given viewport fits within this viewport.
// Arguments:
// - other - The viewport to fit inside this one
// Return Value:
// - True if it fits. False otherwise.
bool Viewport::IsInBounds(const Viewport& other) const noexcept
{
return other.Left() >= Left() && other.Left() <= RightInclusive() &&
other.RightInclusive() >= Left() && other.RightInclusive() <= RightInclusive() &&
other.Top() >= Top() && other.Top() <= other.BottomInclusive() &&
other.BottomInclusive() >= Top() && other.BottomInclusive() <= BottomInclusive();
}
// Method Description:
// - Determines if the given coordinate position lies within this viewport.
// Arguments:
// - pos - Coordinate position
// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position.
// Used in accessibility to signify that the exclusive end
// includes the last COORD in a given viewport.
// Return Value:
// - True if it lies inside the viewport. False otherwise.
bool Viewport::IsInBounds(const COORD& pos, bool allowEndExclusive) const noexcept
{
if (allowEndExclusive && pos == EndExclusive())
{
return true;
}
return pos.X >= Left() && pos.X < RightExclusive() &&
pos.Y >= Top() && pos.Y < BottomExclusive();
}
// Method Description:
// - Clamps a coordinate position into the inside of this viewport.
// Arguments:
// - pos - coordinate to update/clamp
// Return Value:
// - <none>
void Viewport::Clamp(COORD& pos) const
{
THROW_HR_IF(E_NOT_VALID_STATE, !IsValid()); // we can't clamp to an invalid viewport.
pos.X = std::clamp(pos.X, Left(), RightInclusive());
pos.Y = std::clamp(pos.Y, Top(), BottomInclusive());
}
// Method Description:
// - Clamps a viewport into the inside of this viewport.
// Arguments:
// - other - Viewport to clamp to the inside of this viewport
// Return Value:
// - Clamped viewport
Viewport Viewport::Clamp(const Viewport& other) const noexcept
{
auto clampMe = other.ToInclusive();
clampMe.Left = std::clamp(clampMe.Left, Left(), RightInclusive());
clampMe.Right = std::clamp(clampMe.Right, Left(), RightInclusive());
clampMe.Top = std::clamp(clampMe.Top, Top(), BottomInclusive());
clampMe.Bottom = std::clamp(clampMe.Bottom, Top(), BottomInclusive());
return Viewport::FromInclusive(clampMe);
}
// Method Description:
// - Moves the coordinate given by the number of positions and
// in the direction given (repeated increment or decrement)
// Arguments:
// - move - Magnitude and direction of the move
// - pos - The coordinate position to adjust
// Return Value:
// - True if we successfully moved the requested distance. False if we had to stop early.
// - If False, we will restore the original position to the given coordinate.
bool Viewport::MoveInBounds(const ptrdiff_t move, COORD& pos) const noexcept
{
const auto backup = pos;
bool success = true; // If nothing happens, we're still successful (e.g. add = 0)
for (int i = 0; i < move; i++)
{
success = IncrementInBounds(pos);
// If an operation fails, break.
if (!success)
{
break;
}
}
for (int i = 0; i > move; i--)
{
success = DecrementInBounds(pos);
// If an operation fails, break.
if (!success)
{
break;
}
}
// If any operation failed, revert to backed up state.
if (!success)
{
pos = backup;
}
return success;
}
// Method Description:
// - Increments the given coordinate within the bounds of this viewport.
// Arguments:
// - pos - Coordinate position that will be incremented, if it can be.
// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position.
// Used in accessibility to signify that the exclusive end
// includes the last COORD in a given viewport.
// Return Value:
// - True if it could be incremented. False if it would move outside.
bool Viewport::IncrementInBounds(COORD& pos, bool allowEndExclusive) const noexcept
{
return WalkInBounds(pos, { XWalk::LeftToRight, YWalk::TopToBottom }, allowEndExclusive);
}
// Method Description:
// - Increments the given coordinate within the bounds of this viewport
// rotating around to the top when reaching the bottom right corner.
// Arguments:
// - pos - Coordinate position that will be incremented.
// Return Value:
// - True if it could be incremented inside the viewport.
// - False if it rolled over from the bottom right corner back to the top.
bool Viewport::IncrementInBoundsCircular(COORD& pos) const noexcept
{
return WalkInBoundsCircular(pos, { XWalk::LeftToRight, YWalk::TopToBottom });
}
// Method Description:
// - Decrements the given coordinate within the bounds of this viewport.
// Arguments:
// - pos - Coordinate position that will be incremented, if it can be.
// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position.
// Used in accessibility to signify that the exclusive end
// includes the last COORD in a given viewport.
// Return Value:
// - True if it could be incremented. False if it would move outside.
bool Viewport::DecrementInBounds(COORD& pos, bool allowEndExclusive) const noexcept
{
return WalkInBounds(pos, { XWalk::RightToLeft, YWalk::BottomToTop }, allowEndExclusive);
}
// Method Description:
// - Decrements the given coordinate within the bounds of this viewport
// rotating around to the bottom right when reaching the top left corner.
// Arguments:
// - pos - Coordinate position that will be decremented.
// Return Value:
// - True if it could be decremented inside the viewport.
// - False if it rolled over from the top left corner back to the bottom right.
bool Viewport::DecrementInBoundsCircular(COORD& pos) const noexcept
{
return WalkInBoundsCircular(pos, { XWalk::RightToLeft, YWalk::BottomToTop });
}
// Routine Description:
// - Compares two coordinate positions to determine whether they're the same, left, or right within the given buffer size
// Arguments:
// - first- The first coordinate position
// - second - The second coordinate position
// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position.
// Used in accessibility to signify that the exclusive end
// includes the last COORD in a given viewport.
// Return Value:
// - Negative if First is to the left of the Second.
// - 0 if First and Second are the same coordinate.
// - Positive if First is to the right of the Second.
// - This is so you can do s_CompareCoords(first, second) <= 0 for "first is left or the same as second".
// (the < looks like a left arrow :D)
// - The magnitude of the result is the distance between the two coordinates when typing characters into the buffer (left to right, top to bottom)
int Viewport::CompareInBounds(const COORD& first, const COORD& second, bool allowEndExclusive) const noexcept
{
// Assert that our coordinates are within the expected boundaries
FAIL_FAST_IF(!IsInBounds(first, allowEndExclusive));
FAIL_FAST_IF(!IsInBounds(second, allowEndExclusive));
// First set the distance vertically
// If first is on row 4 and second is on row 6, first will be -2 rows behind second * an 80 character row would be -160.
// For the same row, it'll be 0 rows * 80 character width = 0 difference.
int retVal = (first.Y - second.Y) * Width();
// Now adjust for horizontal differences
// If first is in position 15 and second is in position 30, first is -15 left in relation to 30.
retVal += (first.X - second.X);
// Further notes:
// If we already moved behind one row, this will help correct for when first is right of second.
// For example, with row 4, col 79 and row 5, col 0 as first and second respectively, the distance is -1.
// Assume the row width is 80.
// Step one will set the retVal as -80 as first is one row behind the second.
// Step two will then see that first is 79 - 0 = +79 right of second and add 79
// The total is -80 + 79 = -1.
return retVal;
}
// Method Description:
// - Walks the given coordinate within the bounds of this viewport in the specified
// X and Y directions.
// Arguments:
// - pos - Coordinate position that will be adjusted, if it can be.
// - dir - Walking direction specifying which direction to go when reaching the end of a row/column
// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position.
// Used in accessibility to signify that the exclusive end
// includes the last COORD in a given viewport.
// Return Value:
// - True if it could be adjusted as specified and remain in bounds. False if it would move outside.
bool Viewport::WalkInBounds(COORD& pos, const WalkDir dir, bool allowEndExclusive) const noexcept
{
auto copy = pos;
if (WalkInBoundsCircular(copy, dir, allowEndExclusive))
{
pos = copy;
return true;
}
else
{
return false;
}
}
// Method Description:
// - Walks the given coordinate within the bounds of this viewport
// rotating around to the opposite corner when reaching the final corner
// in the specified direction.
// Arguments:
// - pos - Coordinate position that will be adjusted.
// - dir - Walking direction specifying which direction to go when reaching the end of a row/column
// - allowEndExclusive - if true, allow the EndExclusive COORD as a valid position.
// Used in accessibility to signify that the exclusive end
// includes the last COORD in a given viewport.
// Return Value:
// - True if it could be adjusted inside the viewport.
// - False if it rolled over from the final corner back to the initial corner
// for the specified walk direction.
bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir, bool allowEndExclusive) const noexcept
{
// Assert that the position given fits inside this viewport.
FAIL_FAST_IF(!IsInBounds(pos, allowEndExclusive));
if (dir.x == XWalk::LeftToRight)
{
if (allowEndExclusive && pos.X == Left() && pos.Y == BottomExclusive())
{
pos.Y = Top();
return false;
}
else if (pos.X == RightInclusive())
{
pos.X = Left();
if (dir.y == YWalk::TopToBottom)
{
pos.Y++;
if (allowEndExclusive && pos.Y == BottomExclusive())
{
return true;
}
else if (pos.Y > BottomInclusive())
{
pos.Y = Top();
return false;
}
}
else
{
pos.Y--;
if (pos.Y < Top())
{
pos.Y = BottomInclusive();
return false;
}
}
}
else
{
pos.X++;
}
}
else
{
if (pos.X == Left())
{
pos.X = RightInclusive();
if (dir.y == YWalk::TopToBottom)
{
pos.Y++;
if (pos.Y > BottomInclusive())
{
pos.Y = Top();
return false;
}
}
else
{
pos.Y--;
if (pos.Y < Top())
{
pos.Y = BottomInclusive();
return false;
}
}
}
else
{
pos.X--;
}
}
return true;
}
// Routine Description:
// - If walking through a viewport, one might want to know the origin
// for the direction walking.
// - For example, for walking up and to the left (bottom right corner
// to top left corner), the origin would start at the bottom right.
// Arguments:
// - dir - The direction one intends to walk through the viewport
// Return Value:
// - The origin for the walk to reach every position without circling
// if using this same viewport with the `WalkInBounds` methods.
COORD Viewport::GetWalkOrigin(const WalkDir dir) const noexcept
{
COORD origin{ 0 };
origin.X = dir.x == XWalk::LeftToRight ? Left() : RightInclusive();
origin.Y = dir.y == YWalk::TopToBottom ? Top() : BottomInclusive();
return origin;
}
// Routine Description:
// - Given two viewports that will be used for copying data from one to the other (source, target),
// determine which direction you will have to walk through them to ensure that an overlapped copy
// won't erase data in the source that hasn't yet been read and copied into the target at the same
// coordinate offset position from their respective origins.
// - Note: See elaborate ASCII-art comment inside the body of this function for more details on how/why this works.
// Arguments:
// - source - The viewport representing the region that will be copied from
// - target - The viewport representing the region that will be copied to
// Return Value:
// - The direction to walk through both viewports from the walk origins to touch every cell and not
// accidentally overwrite something that hasn't been read yet. (use with GetWalkOrigin and WalkInBounds)
Viewport::WalkDir Viewport::DetermineWalkDirection(const Viewport& source, const Viewport& target) noexcept
{
// We can determine which direction we need to walk based on solely the origins of the two rectangles.
// I'll use a few examples to prove the situation.
//
// For the cardinal directions, let's start with this sample:
//
// source target
// origin 0,0 origin 4,0
// | |
// v V
// +--source-----+--target--------- +--source-----+--target---------
// | A B C D | E | 1 2 3 4 | becomes | A B C D | A | B C D E |
// | F G H I | J | 5 6 7 8 | =========> | F G H I | F | G H I J |
// | K L M N | O | 9 $ % @ | | K L M N | K | L M N O |
// -------------------------------- --------------------------------
//
// The source and target overlap in the 5th column (X=4).
// To ensure that we don't accidentally write over the source
// data before we copy it into the target, we want to start by
// reading that column (a.k.a. writing to the farthest away column
// of the target).
//
// This means we want to copy from right to left.
// Top to bottom and bottom to top don't really matter for this since it's
// a cardinal direction shift.
//
// If we do the right most column first as so...
//
// +--source-----+--target--------- +--source-----+--target---------
// | A B C D | E | 1 2 3 4 | step 1 | A B C D | E | 1 2 3 E |
// | F G H I | J | 5 6 7 8 | =========> | F G H I | J | 5 6 7 J |
// | K L M N | O | 9 $ % @ | | K L M N | O | 9 $ % O |
// -------------------------------- --------------------------------
//
// ... then we can see that the EJO column is safely copied first out of the way and
// can be overwritten on subsequent steps without losing anything.
// The rest of the columns aren't overlapping, so they'll be fine.
//
// But we extrapolate this logic to follow for rectangles that overlap more columns, up
// to and including only leaving one column not overlapped...
//
// source target
// origin origin
// 0,0 / 1,0
// | /
// v v
// +----+------target- +----+------target-
// | A | B C D | E | becomes | A | A B C | D |
// | F | G H I | J | =========> | F | F G H | I |
// | K | L M N | O | | K | K L M | N |
// ---source---------- ---source----------
//
// ... will still be OK following the same Right-To-Left rule as the first move.
//
// +----+------target- +----+------target-
// | A | B C D | E | step 1 | A | B C D | D |
// | F | G H I | J | =========> | F | G H I | I |
// | K | L M N | O | | K | L M N | N |
// ---source---------- ---source----------
//
// The DIN column from the source was moved to the target as the right most column
// of both rectangles. Now it is safe to iterate to the second column from the right
// and proceed with moving CHM on top of the source DIN as it was already moved.
//
// +----+------target- +----+------target-
// | A | B C D | E | step 2 | A | B C C | D |
// | F | G H I | J | =========> | F | G H H | I |
// | K | L M N | O | | K | L M M | N |
// ---source---------- ---source----------
//
// Continue walking right to left (an exercise left to the reader,) and we never lose
// any source data before it reaches the target with the Right To Left pattern.
//
// We notice that the target origin was Right of the source origin in this circumstance,
// (target origin X is > source origin X)
// so it is asserted that targets right of sources means that we should "walk" right to left.
//
// Reviewing the above, it doesn't appear to matter if we go Top to Bottom or Bottom to Top,
// so the conclusion is drawn that it doesn't matter as long as the source and target origin
// Y values are the same.
//
// Also, extrapolating this cardinal direction move to the other 3 cardinal directions,
// it should follow that they would follow the same rules.
// That is, a target left of a source, or a Westbound move, opposite of the above Eastbound move,
// should be "walked" left to right.
// (target origin X is < source origin X)
//
// We haven't given the sample yet that Northbound and Southbound moves are the same, but we
// could reason that the same logic applies and the conclusion would be a Northbound move
// would walk from the target toward the source again... a.k.a. Top to Bottom.
// (target origin Y is < source origin Y)
// Then the Southbound move would be the opposite, Bottom to Top.
// (target origin Y is > source origin Y)
//
// To confirm, let's try one more example but moving both at once in an ordinal direction Northeast.
//
// target
// origin 1, 0
// |
// v
// +----target-- +----target--
// source A | B C | A | D E |
// origin-->+------------ | becomes +------------ |
// 0, 1 | D | E | F | =========> | D | G | H |
// | ------------- | -------------
// | G H | I | G H | I
// --source----- --source-----
//
// Following our supposed rules from above, we have...
// Source Origin X = 0, Y = 1
// Target Origin X = 1, Y = 0
//
// Source Origin X < Target Origin X which means Right to Left
// Source Origin Y > Target Origin Y which means Top to Bottom
//
// So the first thing we should copy is the Top and Right most
// value from source to target.
//
// +----target-- +----target--
// A | B C | A | B E |
// +------------ | step 1 +------------ |
// | D | E | F | =========> | D | E | F |
// | ------------- | -------------
// | G H | I | G H | I
// --source----- --source-----
//
// And look. The E which was in the overlapping part of the source
// is the first thing copied out of the way and we're safe to copy the rest.
//
// We assume that this pattern then applies to all ordinal directions as well
// and it appears our rules hold.
//
// We've covered all cardinal and ordinal directions... all that is left is two
// rectangles of the same size and origin... and in that case, it doesn't matter
// as nothing is moving and therefore can't be covered up or lost.
//
// Therefore, we will codify our inequalities below as determining the walk direction
// for a given source and target viewport and use the helper `GetWalkOrigin`
// to return the place that we should start walking from when the copy commences.
const auto sourceOrigin = source.Origin();
const auto targetOrigin = target.Origin();
return Viewport::WalkDir{ targetOrigin.X < sourceOrigin.X ? Viewport::XWalk::LeftToRight : Viewport::XWalk::RightToLeft,
targetOrigin.Y < sourceOrigin.Y ? Viewport::YWalk::TopToBottom : Viewport::YWalk::BottomToTop };
}
// Method Description:
// - Clips the input rectangle to our bounds. Assumes that the input rectangle
//is an exclusive rectangle.
// Arguments:
// - psr: a pointer to an exclusive rect to clip.
// Return Value:
// - true iff the clipped rectangle is valid (with a width and height both >0)
bool Viewport::TrimToViewport(_Inout_ SMALL_RECT* const psr) const noexcept
{
psr->Left = std::max(psr->Left, Left());
psr->Right = std::min(psr->Right, RightExclusive());
psr->Top = std::max(psr->Top, Top());
psr->Bottom = std::min(psr->Bottom, BottomExclusive());
return psr->Left < psr->Right && psr->Top < psr->Bottom;
}
// Method Description:
// - Translates the input SMALL_RECT out of our coordinate space, whose origin is
// at (this.Left, this.Right)
// Arguments:
// - psr: a pointer to a SMALL_RECT the translate into our coordinate space.
// Return Value:
// - <none>
void Viewport::ConvertToOrigin(_Inout_ SMALL_RECT* const psr) const noexcept
{
const short dx = Left();
const short dy = Top();
psr->Left -= dx;
psr->Right -= dx;
psr->Top -= dy;
psr->Bottom -= dy;
}
// Method Description:
// - Translates the input coordinate out of our coordinate space, whose origin is
// at (this.Left, this.Right)
// Arguments:
// - pcoord: a pointer to a coordinate the translate into our coordinate space.
// Return Value:
// - <none>
void Viewport::ConvertToOrigin(_Inout_ COORD* const pcoord) const noexcept
{
pcoord->X -= Left();
pcoord->Y -= Top();
}
// Method Description:
// - Translates the input SMALL_RECT to our coordinate space, whose origin is
// at (this.Left, this.Right)
// Arguments:
// - psr: a pointer to a SMALL_RECT the translate into our coordinate space.
// Return Value:
// - <none>
void Viewport::ConvertFromOrigin(_Inout_ SMALL_RECT* const psr) const noexcept
{
const short dx = Left();
const short dy = Top();
psr->Left += dx;
psr->Right += dx;
psr->Top += dy;
psr->Bottom += dy;
}
// Method Description:
// - Translates the input coordinate to our coordinate space, whose origin is
// at (this.Left, this.Right)
// Arguments:
// - pcoord: a pointer to a coordinate the translate into our coordinate space.
// Return Value:
// - <none>
void Viewport::ConvertFromOrigin(_Inout_ COORD* const pcoord) const noexcept
{
pcoord->X += Left();
pcoord->Y += Top();
}
// Method Description:
// - Returns an exclusive SMALL_RECT equivalent to this viewport.
// Arguments:
// - <none>
// Return Value:
// - an exclusive SMALL_RECT equivalent to this viewport.
SMALL_RECT Viewport::ToExclusive() const noexcept
{
return { Left(), Top(), RightExclusive(), BottomExclusive() };
}
// Method Description:
// - Returns an exclusive RECT equivalent to this viewport.
// Arguments:
// - <none>
// Return Value:
// - an exclusive RECT equivalent to this viewport.
RECT Viewport::ToRect() const noexcept
{
RECT r{ 0 };
r.left = Left();
r.top = Top();
r.right = RightExclusive();
r.bottom = BottomExclusive();
return r;
}
// Method Description:
// - Returns an inclusive SMALL_RECT equivalent to this viewport.
// Arguments:
// - <none>
// Return Value:
// - an inclusive SMALL_RECT equivalent to this viewport.
SMALL_RECT Viewport::ToInclusive() const noexcept
{
return { Left(), Top(), RightInclusive(), BottomInclusive() };
}
// Method Description:
// - Returns a new viewport representing this viewport at the origin.
// For example:
// this = {6, 5, 11, 11} (w, h = 5, 6)
// result = {0, 0, 5, 6} (w, h = 5, 6)
// Arguments:
// - <none>
// Return Value:
// - a new viewport with the same dimensions as this viewport with top, left = 0, 0
Viewport Viewport::ToOrigin() const noexcept
{
Viewport returnVal = *this;
ConvertToOrigin(&returnVal._sr);
return returnVal;
}
// Method Description:
// - Translates another viewport to this viewport's coordinate space.
// For example:
// this = {5, 6, 7, 8} (w,h = 1, 1)
// other = {6, 5, 11, 11} (w, h = 5, 6)
// result = {1, -1, 6, 5} (w, h = 5, 6)
// Arguments:
// - other: the viewport to convert to this coordinate space
// Return Value:
// - the input viewport in a the coordinate space with origin at (this.Top, this.Left)
[[nodiscard]] Viewport Viewport::ConvertToOrigin(const Viewport& other) const noexcept
{
Viewport returnVal = other;
ConvertToOrigin(&returnVal._sr);
return returnVal;
}
// Method Description:
// - Translates another viewport out of this viewport's coordinate space.
// For example:
// this = {5, 6, 7, 8} (w,h = 1, 1)
// other = {0, 0, 5, 6} (w, h = 5, 6)
// result = {5, 6, 10, 12} (w, h = 5, 6)
// Arguments:
// - other: the viewport to convert out of this coordinate space
// Return Value:
// - the input viewport in a the coordinate space with origin at (0, 0)
[[nodiscard]] Viewport Viewport::ConvertFromOrigin(const Viewport& other) const noexcept
{
Viewport returnVal = other;
ConvertFromOrigin(&returnVal._sr);
return returnVal;
}
// Function Description:
// - Translates a given Viewport by the specified coord amount. Does the
// addition with safemath.
// Arguments:
// - original: The initial viewport to translate. Is unmodified by this operation.
// - delta: The amount to translate the original rect by, in both the x and y coordinates.
// Return Value:
// - The offset viewport by the given delta.
// - NOTE: Throws on safe math failure.
[[nodiscard]] Viewport Viewport::Offset(const Viewport& original, const COORD delta)
{
// If there's no delta, do nothing.
if (delta.X == 0 && delta.Y == 0)
{
return original;
}
SHORT newTop = original._sr.Top;
SHORT newLeft = original._sr.Left;
SHORT newRight = original._sr.Right;
SHORT newBottom = original._sr.Bottom;
THROW_IF_FAILED(ShortAdd(newLeft, delta.X, &newLeft));
THROW_IF_FAILED(ShortAdd(newRight, delta.X, &newRight));
THROW_IF_FAILED(ShortAdd(newTop, delta.Y, &newTop));
THROW_IF_FAILED(ShortAdd(newBottom, delta.Y, &newBottom));
return Viewport({ newLeft, newTop, newRight, newBottom });
}
// Function Description:
// - Returns a viewport created from the union of both the parameter viewports.
// The result extends from the leftmost extent of either rect to the
// rightmost extent of either rect, and from the lowest top value to the
// highest bottom value, and everything in between.
// Arguments:
// - lhs: one of the viewports to or together
// - rhs: the other viewport to or together
// Return Value:
// - a Viewport representing the union of the other two viewports.
[[nodiscard]] Viewport Viewport::Union(const Viewport& lhs, const Viewport& rhs) noexcept
{
const auto leftValid = lhs.IsValid();
const auto rightValid = rhs.IsValid();
// If neither are valid, return empty.
if (!leftValid && !rightValid)
{
return Viewport::Empty();
}
// If left isn't valid, then return just the right.
else if (!leftValid)
{
return rhs;
}
// If right isn't valid, then return just the left.
else if (!rightValid)
{
return lhs;
}
// Otherwise, everything is valid. Find the actual union.
else
{
const auto left = std::min(lhs.Left(), rhs.Left());
const auto top = std::min(lhs.Top(), rhs.Top());
const auto right = std::max(lhs.RightInclusive(), rhs.RightInclusive());
const auto bottom = std::max(lhs.BottomInclusive(), rhs.BottomInclusive());
return Viewport({ left, top, right, bottom });
}
}
// Function Description:
// - Creates a viewport from the intersection fo both the parameter viewports.
// The result will be the smallest area that fits within both rectangles.
// Arguments:
// - lhs: one of the viewports to intersect
// - rhs: the other viewport to intersect
// Return Value:
// - a Viewport representing the intersection of the other two, or an empty viewport if there's no intersection.
[[nodiscard]] Viewport Viewport::Intersect(const Viewport& lhs, const Viewport& rhs) noexcept
{
const auto left = std::max(lhs.Left(), rhs.Left());
const auto top = std::max(lhs.Top(), rhs.Top());
const auto right = std::min(lhs.RightInclusive(), rhs.RightInclusive());
const auto bottom = std::min(lhs.BottomInclusive(), rhs.BottomInclusive());
const Viewport intersection({ left, top, right, bottom });
// What we calculated with min/max might not actually represent a valid viewport that has area.
// If we calculated something that is nonsense (invalid), then just return the empty viewport.
if (!intersection.IsValid())
{
return Viewport::Empty();
}
else
{
// If it was valid, give back whatever we created.
return intersection;
}
}
// Routine Description:
// - Returns a list of Viewports representing the area from the `original` Viewport that was NOT a part of
// the given `removeMe` Viewport. It can require multiple Viewports to represent the remaining
// area as a "region".
// Arguments:
// - original - The overall viewport to start from.
// - removeMe - The space that should be taken out of the main Viewport.
// Return Value:
// - Array of 4 Viewports representing non-overlapping segments of the remaining area
// that was covered by `main` before the regional area of `removeMe` was taken out.
// - You must check that each viewport .IsValid() before using it.
[[nodiscard]] SomeViewports Viewport::Subtract(const Viewport& original, const Viewport& removeMe) noexcept
try
{
SomeViewports result;
// We could have up to four rectangles describing the area resulting when you take removeMe out of main.
// Find the intersection of the two so we know which bits of removeMe are actually applicable
// to the original rectangle for subtraction purposes.
const auto intersection = Viewport::Intersect(original, removeMe);
// If there's no intersection, there's nothing to remove.
if (!intersection.IsValid())
{
// Just put the original rectangle into the results and return early.
result.push_back(original);
}
// If the original rectangle matches the intersection, there is nothing to return.
else if (original != intersection)
{
// Generate our potential four viewports that represent the region of the original that falls outside of the remove area.
// We will bias toward generating wide rectangles over tall rectangles (if possible) so that optimizations that apply
// to manipulating an entire row at once can be realized by other parts of the console code. (i.e. Run Length Encoding)
// In the following examples, the found remaining regions are represented by:
// T = Top B = Bottom L = Left R = Right
//
// 4 Sides but Identical:
// |---------original---------| |---------original---------|
// | | | |
// | | | |
// | | | |
// | | ======> | intersect | ======> early return of nothing
// | | | |
// | | | |
// | | | |
// |---------removeMe---------| |--------------------------|
//
// 4 Sides:
// |---------original---------| |---------original---------| |--------------------------|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | |---------| | | |---------| | |LLLLLLLL|---------|RRRRRRR|
// | |removeMe | | ======> | |intersect| | ======> |LLLLLLLL| |RRRRRRR|
// | |---------| | | |---------| | |LLLLLLLL|---------|RRRRRRR|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// |--------------------------| |--------------------------| |--------------------------|
//
// 3 Sides:
// |---------original---------| |---------original---------| |--------------------------|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
// | |removeMe | ======> | |intersect | ======> |LLLLLLLL| |
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// |--------------------------| |--------------------------| |--------------------------|
//
// 2 Sides: