aboutsummaryrefslogtreecommitdiffstats
path: root/Lib/defconQt/util/uiMethods.py
blob: 8666203cb5391888268e2ae4510de9db31c8c372 (plain)
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
"""
UI-constrained point management methods.
"""
from PyQt5.QtCore import QLineF, QPointF


def _getOffCurveSiblingPoints(contour, point):
    index = contour.index(point)
    for d in (-1, 1):
        sibling = contour.getPoint(index + d)
        if sibling.segmentType is not None:
            sSibling = contour.getPoint(index + 2 * d)
            return sibling, sSibling
    raise IndexError


def moveUIPoint(contour, point, delta):
    if point.segmentType is None:
        # point is an offCurve. Get its sibling onCurve and the other
        # offCurve.
        onCurve, otherPoint = _getOffCurveSiblingPoints(contour, point)
        # if the onCurve is selected, the offCurve will move along with it
        if onCurve.selected:
            return
        point.move(delta)
        if not onCurve.smooth:
            contour.dirty = True
            return
        # if the onCurve is smooth, we need to either...
        if otherPoint.segmentType is None and not otherPoint.selected:
            # keep the other offCurve inline
            line = QLineF(point.x, point.y, onCurve.x, onCurve.y)
            otherLine = QLineF(
                onCurve.x, onCurve.y, otherPoint.x, otherPoint.y)
            line.setLength(line.length() + otherLine.length())
            otherPoint.x = line.x2()
            otherPoint.y = line.y2()
        else:
            # keep point in tangency with onCurve -> otherPoint segment,
            # ie. do an orthogonal projection
            line = QLineF(otherPoint.x, otherPoint.y, onCurve.x, onCurve.y)
            n = line.normalVector()
            n.translate(QPointF(point.x, point.y) - n.p1())
            targetPoint = QPointF()
            n.intersect(line, targetPoint)
            # check that targetPoint is beyond its neighbor onCurve
            # we do this by calculating position of the offCurve and second
            # onCurve relative to the first onCurve. If there is no symmetry
            # in at least one of the axis, then we need to clamp
            onCurvePoint = line.p2()
            onDistance = line.p1() - onCurvePoint
            newDistance = targetPoint - onCurvePoint
            if (onDistance.x() >= 0) != (newDistance.x() <= 0) or \
                    (onDistance.y() >= 0) != (newDistance.y() <= 0):
                targetPoint = onCurvePoint
            # ok, now set pos
            point.x, point.y = targetPoint.x(), targetPoint.y()
    else:
        # point is an onCurve. Move its offCurves along with it.
        index = contour.index(point)
        point.move(delta)
        for d in (-1, 1):
            # edge-case: contour open, trailing offCurve and moving first
            # onCurve in contour
            if contour.open and index == 0 and d == -1:
                continue
            pt = contour.getPoint(index + d)
            if pt.segmentType is None:
                pt.move(delta)
    contour.dirty = True


def moveUISelection(contour, delta):
    for point in contour.selection:
        moveUIPoint(contour, point, delta)


def removeUISelection(contour, preserveShape=True):
    segments = contour.segments
    # the last segments contains the first point, make sure to process it last
    # so as to not offset indexes
    toFirstPoint = segments[-1]
    toIter = list(enumerate(segments))
    toIter.insert(0, toIter.pop())
    # moonwalk through segments
    for index, segment in reversed(toIter):
        if segment == toFirstPoint:
            index = len(segments) - 1
        onCurve = segment[-1]
        # if the onCurve is selected, wipe it
        if onCurve.selected:
            # remove the contour if we have exhausted segments
            if len(segments) < 2:
                glyph = contour.glyph
                glyph.removeContour(contour)
                return
            # using preserveShape at the edge of an open contour will traceback
            if preserveShape and contour.open:
                if index in (0, len(segments) - 1):
                    preserveShape = False
            contour.removeSegment(index, preserveShape)
            # remove segment so we can keep track of how many remain
            del segments[index]
        elif len(segment) == 2:
            # move with trailing offCurve
            offCurve = segment[0]
            if offCurve.selected:
                assert(offCurve.segmentType is None)
                contour.removePoint(offCurve)
        elif len(segment) == 3:
            # if offCurve selected, wipe them
            for i in (0, 1):
                if segment[i].selected:
                    contour.removePoint(segment[0])
                    contour.removePoint(segment[1])
                    segment[2].segmentType = "line"
                    break