mirror of
https://github.com/coteditor/CotEditor.git
synced 2024-09-20 23:58:08 +03:00
Refactor OpacitySlider
This commit is contained in:
parent
3cccfd040b
commit
6cf3b15ac4
@ -633,6 +633,8 @@
|
||||
2ACC65321C98033D000574DC /* ThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACC65311C98033D000574DC /* ThemeTests.swift */; };
|
||||
2ACD02BD22A87EFD00893051 /* ColorCode in Frameworks */ = {isa = PBXBuildFile; productRef = 2ACD02BC22A87EFD00893051 /* ColorCode */; };
|
||||
2ACD02BF22A87F0400893051 /* ColorCode in Frameworks */ = {isa = PBXBuildFile; productRef = 2ACD02BE22A87F0400893051 /* ColorCode */; };
|
||||
2ACDA2502B81201A00B2EBA8 /* OpacitySlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */; };
|
||||
2ACDA2512B81201A00B2EBA8 /* OpacitySlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */; };
|
||||
2ACDC0911D1726BD009B72D6 /* DotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDC0901D1726BD009B72D6 /* DotView.swift */; };
|
||||
2ACDC0921D1726BD009B72D6 /* DotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDC0901D1726BD009B72D6 /* DotView.swift */; };
|
||||
2ACDC0971D172B2A009B72D6 /* PaddingTextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDC0961D172B2A009B72D6 /* PaddingTextFieldCell.swift */; };
|
||||
@ -1193,6 +1195,7 @@
|
||||
2ACC21B41E52B8C50078241F /* DefaultKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultKeys.swift; sourceTree = "<group>"; };
|
||||
2ACC5E401E7B08D300109ABC /* MultipleReplaceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleReplaceViewController.swift; sourceTree = "<group>"; };
|
||||
2ACC65311C98033D000574DC /* ThemeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeTests.swift; sourceTree = "<group>"; };
|
||||
2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpacitySlider.swift; sourceTree = "<group>"; };
|
||||
2ACDC0901D1726BD009B72D6 /* DotView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotView.swift; sourceTree = "<group>"; };
|
||||
2ACDC0961D172B2A009B72D6 /* PaddingTextFieldCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaddingTextFieldCell.swift; sourceTree = "<group>"; };
|
||||
2ACDC0991D172CDE009B72D6 /* AntialiasingTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AntialiasingTextField.swift; sourceTree = "<group>"; };
|
||||
@ -2232,6 +2235,7 @@
|
||||
2A59B7022957089A0094F03B /* LinkButton.swift */,
|
||||
2A4A7D122856FF340085D2E7 /* HelpButton.swift */,
|
||||
2A2615882977FCF6008C2240 /* SubmitButtonGroup.swift */,
|
||||
2ACDA24F2B81201A00B2EBA8 /* OpacitySlider.swift */,
|
||||
2A5D13121D1EE8FF00D38E6A /* HUDView.swift */,
|
||||
2AA175F92AC5634500F6462C /* PopoverHolderView.swift */,
|
||||
2AE144C32B0222DB005E8CF1 /* LiveTextInsertionView.swift */,
|
||||
@ -2891,6 +2895,7 @@
|
||||
2AD8D74B2064AD83000BEFDB /* NumberTextField.swift in Sources */,
|
||||
2AC3845420C929950003F213 /* OpacitySampleView.swift in Sources */,
|
||||
2A55D5EA2B7A86190092DE48 /* IssueReport.swift in Sources */,
|
||||
2ACDA2502B81201A00B2EBA8 /* OpacitySlider.swift in Sources */,
|
||||
2A158C222945F54B000A4EC1 /* OpacityView.swift in Sources */,
|
||||
2AC6069C20416ADE00F9C839 /* OpenPanelAccessory.swift in Sources */,
|
||||
2A3E61BF27C3795B00C6E5B6 /* OptionalMenu.swift in Sources */,
|
||||
@ -3253,6 +3258,7 @@
|
||||
2AD8D74A2064AD83000BEFDB /* NumberTextField.swift in Sources */,
|
||||
2AC3845320C929950003F213 /* OpacitySampleView.swift in Sources */,
|
||||
2A158C232945F54B000A4EC1 /* OpacityView.swift in Sources */,
|
||||
2ACDA2512B81201A00B2EBA8 /* OpacitySlider.swift in Sources */,
|
||||
2AC6069B20416ADE00F9C839 /* OpenPanelAccessory.swift in Sources */,
|
||||
2A3E61C027C3795B00C6E5B6 /* OptionalMenu.swift in Sources */,
|
||||
2A88E7711E81A2C7000019C6 /* OrderedSet.swift in Sources */,
|
||||
|
138
CotEditor/Sources/OpacitySlider.swift
Normal file
138
CotEditor/Sources/OpacitySlider.swift
Normal file
@ -0,0 +1,138 @@
|
||||
//
|
||||
// OpacitySlider.swift
|
||||
//
|
||||
// CotEditor
|
||||
// https://coteditor.com
|
||||
//
|
||||
// Created by 1024jp on 2024-02-18.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// © 2024 1024jp
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct OpacitySlider: View {
|
||||
|
||||
@Binding var value: Double
|
||||
|
||||
var bounds: ClosedRange<Double> = 0.2...1
|
||||
var step: Double?
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
if let step {
|
||||
Slider(value: $value, in: self.bounds, step: step) {
|
||||
EmptyView()
|
||||
} minimumValueLabel: {
|
||||
OpacitySample(opacity: self.bounds.lowerBound)
|
||||
.help(self.minimumHelp)
|
||||
} maximumValueLabel: {
|
||||
OpacitySample(opacity: self.bounds.upperBound)
|
||||
.help(self.maximumHelp)
|
||||
}
|
||||
} else {
|
||||
Slider(value: $value, in: self.bounds) {
|
||||
EmptyView()
|
||||
} minimumValueLabel: {
|
||||
OpacitySample(opacity: self.bounds.lowerBound)
|
||||
.help(self.minimumHelp)
|
||||
} maximumValueLabel: {
|
||||
OpacitySample(opacity: self.bounds.upperBound)
|
||||
.help(self.maximumHelp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var minimumHelp: String {
|
||||
|
||||
String(localized: "Transparent", comment: "tooltip for opacity slider")
|
||||
}
|
||||
|
||||
|
||||
private var maximumHelp: String {
|
||||
|
||||
String(localized: "Opaque", comment: "tooltip for opacity slider")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private struct OpacitySample: View {
|
||||
|
||||
let opacity: Double
|
||||
|
||||
private let inset: Double = 3
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
GeometryReader { geometry in
|
||||
let radius = geometry.size.height / 4
|
||||
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
||||
.fill(.background)
|
||||
|
||||
Triangle()
|
||||
.fill(.primary)
|
||||
.opacity(1 - self.opacity)
|
||||
.clipShape(RoundedRectangle(cornerRadius: radius, style: .continuous)
|
||||
.inset(by: self.inset))
|
||||
|
||||
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
||||
.inset(by: 0.5)
|
||||
.stroke(.tertiary, lineWidth: 1)
|
||||
}
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 16)
|
||||
}
|
||||
|
||||
|
||||
private struct Triangle: Shape {
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
|
||||
Path { path in
|
||||
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
|
||||
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
|
||||
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
|
||||
path.closeSubpath()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
@available(macOS 14, *)
|
||||
#Preview(traits: .fixedLayout(width: 200, height: 50)) {
|
||||
|
||||
VStack {
|
||||
OpacitySlider(value: .constant(0.6))
|
||||
OpacitySlider(value: .constant(0.6), step: 0.2)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
#Preview("OpacitySample") {
|
||||
OpacitySample(opacity: 0.5)
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
@ -58,7 +58,6 @@ import SwiftUI
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct OpacityView: View {
|
||||
|
||||
weak var window: DocumentWindow?
|
||||
@ -72,7 +71,6 @@ struct OpacityView: View {
|
||||
Text("Editor’s Opacity")
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.secondary)
|
||||
.labelsHidden()
|
||||
|
||||
OpacitySlider(value: $opacity)
|
||||
.onChange(of: self.opacity) { newValue in
|
||||
@ -93,97 +91,8 @@ struct OpacityView: View {
|
||||
|
||||
|
||||
|
||||
private struct OpacitySlider: View {
|
||||
|
||||
@Binding private var value: Double
|
||||
|
||||
private let bounds: ClosedRange<Double>
|
||||
private let label: LocalizedStringKey?
|
||||
|
||||
|
||||
init(_ label: LocalizedStringKey? = nil, value: Binding<Double>, in bounds: ClosedRange<Double> = 0.2...1) {
|
||||
|
||||
self._value = value
|
||||
self.bounds = bounds
|
||||
self.label = label
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
Slider(value: $value, in: self.bounds) {
|
||||
if let label {
|
||||
Text(label)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
} minimumValueLabel: {
|
||||
OpacitySample(opacity: self.bounds.lowerBound)
|
||||
.help("Transparent")
|
||||
.frame(width: 16, height: 16)
|
||||
} maximumValueLabel: {
|
||||
OpacitySample(opacity: self.bounds.upperBound)
|
||||
.help("Opaque")
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private struct OpacitySample: View {
|
||||
|
||||
let opacity: Double
|
||||
|
||||
private let inset: Double = 3
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
GeometryReader { geometry in
|
||||
let radius = geometry.size.height / 4
|
||||
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
||||
.fill(.background)
|
||||
|
||||
Triangle()
|
||||
.fill(.primary)
|
||||
.opacity(1 - self.opacity)
|
||||
.clipShape(RoundedRectangle(cornerRadius: radius, style: .continuous)
|
||||
.inset(by: self.inset))
|
||||
|
||||
RoundedRectangle(cornerRadius: radius, style: .continuous)
|
||||
.inset(by: 0.5)
|
||||
.stroke(.tertiary, lineWidth: 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private struct Triangle: Shape {
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
|
||||
Path { path in
|
||||
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
|
||||
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
|
||||
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
|
||||
path.closeSubpath()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
OpacityView()
|
||||
}
|
||||
|
||||
#Preview("OpacitySample") {
|
||||
OpacitySample(opacity: 0.5)
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user