From c3d36286812cd495bb45f06026c234c6bc547511 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 28 Apr 2022 02:04:15 +0100 Subject: [PATCH 001/133] Expose textColor and caretColor APIs --- mathEditor/editor/MTEditableMathLabel.h | 2 ++ mathEditor/editor/MTEditableMathLabel.m | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index 318a7fb..209614d 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -66,6 +66,8 @@ @property (nonatomic) MTMathList* mathList; @property (nonatomic) UIColor* highlightColor; +@property (nonatomic) UIColor* textColor; +@property (nonatomic) UIColor* caretColor; @property (nonatomic) UIImageView* cancelImage; @property (nonatomic, weak) id delegate; diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 05f528f..0d732f2 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -117,6 +117,26 @@ -(void)layoutSubviews [self insertionPointChanged]; } +- (void)setTextColor:(UIColor *)textColor +{ + self.label.textColor = textColor; +} + +- (UIColor*)textColor +{ + return self.label.textColor; +} + +- (void)setCaretColor:(UIColor *)caretColor +{ + _caretView.caretColor = caretColor; +} + +- (UIColor *)caretColor +{ + return _caretView.caretColor; +} + - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; From 83148f2ce8fca8ab6878274490ab5711a03da179 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Mon, 11 Jul 2022 01:28:37 +0100 Subject: [PATCH 002/133] Swift package manager --- .gitignore | 4 +++ Package.resolved | 14 +++++++++ Package.swift | 40 +++++++++++++++++++++++++ Tests/MTDisplayEditingTest.m | 1 - mathEditor/MathKeyboardResources | 1 + mathEditor/editor/MTEditableMathLabel.h | 2 +- 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 Package.resolved create mode 100644 Package.swift create mode 120000 mathEditor/MathKeyboardResources diff --git a/.gitignore b/.gitignore index a74cfed..96c6c72 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,7 @@ Carthage/Build fastlane/report.xml fastlane/screenshots + +# Swift Package Manager +.swiftpm/ +.build/ diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..3ea8321 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "iosmath", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maitbayev/iosMath.git", + "state" : { + "branch" : "master", + "revision" : "01ca7209675caa2bb899cfa8d13f94f910657254" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..145c18c --- /dev/null +++ b/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version: 5.6 + +import PackageDescription + +let package = Package( + name: "MathEditor", + defaultLocalization: "en", + platforms: [.iOS(.v13)], + products: [ + .library( + name: "MathEditor", + targets: ["MathEditor"]) + ], + dependencies: [ + .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") + ], + targets: [ + .target( + name: "MathEditor", + dependencies: [.product(name: "iosMath", package: "iosMath")], + path: "./mathEditor", + resources: [.process("MathKeyboardResources")], + cSettings: [ + .headerSearchPath("./editor"), + .headerSearchPath("./keyboard"), + .headerSearchPath("./internal"), + ] + ), + .testTarget( + name: "MathEditorTests", + dependencies: ["MathEditor"], + path: "Tests", + cSettings: [ + .headerSearchPath("../mathEditor/editor"), + .headerSearchPath("../mathEditor/keyboard"), + .headerSearchPath("../mathEditor/internal"), + ] + ), + ] +) diff --git a/Tests/MTDisplayEditingTest.m b/Tests/MTDisplayEditingTest.m index 23adc74..f828b4f 100644 --- a/Tests/MTDisplayEditingTest.m +++ b/Tests/MTDisplayEditingTest.m @@ -17,7 +17,6 @@ #import "MTMathListBuilder.h" #import "MTFontManager.h" #import "MTTypesetter.h" - @interface MTDisplayEditingTest : XCTestCase @property (nonatomic) MTFont* font; diff --git a/mathEditor/MathKeyboardResources b/mathEditor/MathKeyboardResources new file mode 120000 index 0000000..4b9ef22 --- /dev/null +++ b/mathEditor/MathKeyboardResources @@ -0,0 +1 @@ +../MathKeyboardResources \ No newline at end of file diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index 209614d..cf39bbd 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -9,7 +9,7 @@ // #import -#import +#include "MTMathList.h" @class MTEditableMathLabel; @class MTMathListIndex; From d17230547001b36dceec65b96b8a685c7714a123 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Mon, 11 Jul 2022 01:42:59 +0100 Subject: [PATCH 003/133] Fix when consumed from swift --- mathEditor/include/MTEditableMathLabel.h | 1 + mathEditor/include/module.modulemap | 6 ++++++ mathEditor/keyboard/MTMathKeyboardRootView.m | 3 +++ 3 files changed, 10 insertions(+) create mode 120000 mathEditor/include/MTEditableMathLabel.h create mode 100644 mathEditor/include/module.modulemap diff --git a/mathEditor/include/MTEditableMathLabel.h b/mathEditor/include/MTEditableMathLabel.h new file mode 120000 index 0000000..3482464 --- /dev/null +++ b/mathEditor/include/MTEditableMathLabel.h @@ -0,0 +1 @@ +../editor/MTEditableMathLabel.h \ No newline at end of file diff --git a/mathEditor/include/module.modulemap b/mathEditor/include/module.modulemap new file mode 100644 index 0000000..a4d7a86 --- /dev/null +++ b/mathEditor/include/module.modulemap @@ -0,0 +1,6 @@ +module MathEditor { + header "../editor/MTEditableMathLabel.h" + header "../keyboard/MTMathKeyboardRootView.h" + + export * +} diff --git a/mathEditor/keyboard/MTMathKeyboardRootView.m b/mathEditor/keyboard/MTMathKeyboardRootView.m index cdbb35f..77c1d5a 100644 --- a/mathEditor/keyboard/MTMathKeyboardRootView.m +++ b/mathEditor/keyboard/MTMathKeyboardRootView.m @@ -46,6 +46,9 @@ +(instancetype)sharedInstance { // Gets the math keyboard resources bundle +(NSBundle *)getMathKeyboardResourcesBundle { +#ifdef SWIFTPM_MODULE_BUNDLE + return SWIFTPM_MODULE_BUNDLE +#endif return [NSBundle bundleWithURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"MTKeyboardResources" withExtension:@"bundle"]]; } From 0420ed1b454b64a0649d773cc3ab67bb04230e46 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Mon, 11 Jul 2022 02:02:50 +0100 Subject: [PATCH 004/133] Fix build issue --- mathEditor/keyboard/MTMathKeyboardRootView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathEditor/keyboard/MTMathKeyboardRootView.m b/mathEditor/keyboard/MTMathKeyboardRootView.m index 77c1d5a..06696d0 100644 --- a/mathEditor/keyboard/MTMathKeyboardRootView.m +++ b/mathEditor/keyboard/MTMathKeyboardRootView.m @@ -47,7 +47,7 @@ +(instancetype)sharedInstance { +(NSBundle *)getMathKeyboardResourcesBundle { #ifdef SWIFTPM_MODULE_BUNDLE - return SWIFTPM_MODULE_BUNDLE + return SWIFTPM_MODULE_BUNDLE; #endif return [NSBundle bundleWithURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"MTKeyboardResources" withExtension:@"bundle"]]; } From 745cc96cde5cbf60f4c435c2d527b8a5d917cc38 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Sun, 20 Jul 2025 17:12:45 +0100 Subject: [PATCH 005/133] Migrate example to SPM --- MathEditor.xcodeproj/project.pbxproj | 242 +++++------------- .../contents.xcworkspacedata | 2 +- .../xcshareddata/swiftpm/Package.resolved | 15 ++ .../xcschemes/iosMathEditor-Example.xcscheme | 24 +- 4 files changed, 87 insertions(+), 196 deletions(-) create mode 100644 MathEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/MathEditor.xcodeproj/project.pbxproj b/MathEditor.xcodeproj/project.pbxproj index e601607..593056d 100644 --- a/MathEditor.xcodeproj/project.pbxproj +++ b/MathEditor.xcodeproj/project.pbxproj @@ -3,13 +3,10 @@ archiveVersion = 1; classes = { }; - objectVersion = 47; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ - 1385A98064E559BF2F757304 /* libPods-MathEditor_Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8FF6761D7D7AC0EC9F0679C9 /* libPods-MathEditor_Example.a */; }; - 2040F7C6A902C4AEF598B076 /* libPods-MathEditor.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 473720D7C87DFE272F052861 /* libPods-MathEditor.a */; }; - 243FB02EE755A1D41861623B /* libPods-MathEditor_Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 023479B03FDA39233191C9B7 /* libPods-MathEditor_Tests.a */; }; 490BE5781CE6A08100AE31A0 /* MTDisplayEditingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 490BE5771CE6A08100AE31A0 /* MTDisplayEditingTest.m */; }; 49DEC8241CF5523E000053CD /* MTCaretView.m in Sources */ = {isa = PBXBuildFile; fileRef = 49DEC81F1CF5523E000053CD /* MTCaretView.m */; }; 49DEC8251CF5523E000053CD /* MTDisplay+Editing.m in Sources */ = {isa = PBXBuildFile; fileRef = 49DEC8211CF5523E000053CD /* MTDisplay+Editing.m */; }; @@ -29,6 +26,7 @@ 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; + 862D28DD2E2D4C4400F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28DC2E2D4C4400F9B6FE /* MathEditor */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; /* End PBXBuildFile section */ @@ -45,10 +43,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 023479B03FDA39233191C9B7 /* libPods-MathEditor_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MathEditor_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 09D14643E1EDE3F6786E897E /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 1377C04C01CE56EB11AB5A1B /* Pods-MathEditor.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor/Pods-MathEditor.debug.xcconfig"; sourceTree = ""; }; - 473720D7C87DFE272F052861 /* libPods-MathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MathEditor.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 490BE5561CE695CC00AE31A0 /* libMathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMathEditor.a; sourceTree = BUILT_PRODUCTS_DIR; }; 490BE5771CE6A08100AE31A0 /* MTDisplayEditingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTDisplayEditingTest.m; sourceTree = ""; }; 497F92211CF8CBFF00022162 /* MathEditor-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MathEditor-Prefix.pch"; sourceTree = ""; }; @@ -74,7 +69,6 @@ 49DEC83D1CF5591D000053CD /* WhiteBGKeyboardTab.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = WhiteBGKeyboardTab.xcassets; sourceTree = ""; }; 49DEC83E1CF57D75000053CD /* lmroman10-bolditalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "lmroman10-bolditalic.otf"; sourceTree = ""; }; 49DEC83F1CF59F82000053CD /* MathEditor.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = MathEditor.podspec; sourceTree = ""; }; - 5628D2459CDCF8D3064ABC73 /* libPods-iosMathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosMathEditor.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58A195388D20070C39A /* MathEditor_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathEditor_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -92,16 +86,8 @@ 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; - 733BAF45E72F86EDFEAAAD61 /* libPods-iosMathEditor_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosMathEditor_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 753F7D8158AE6B036B248A96 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - 7A378091F23FA26A8EEA2F82 /* Pods-MathEditor_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example.release.xcconfig"; sourceTree = ""; }; 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; - 8A93C35286D2F42BBFFF6281 /* Pods-MathEditor.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor.release.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor/Pods-MathEditor.release.xcconfig"; sourceTree = ""; }; - 8FF6761D7D7AC0EC9F0679C9 /* libPods-MathEditor_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MathEditor_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - A4EC35D984BA58883006D2C3 /* Pods-MathEditor_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests.release.xcconfig"; sourceTree = ""; }; - AA22ED6294D4E5576093F8A8 /* Pods-MathEditor_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests.debug.xcconfig"; sourceTree = ""; }; - AEA5AA8AF2C91D3A7B9B05F9 /* libPods-iosMathEditor_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosMathEditor_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - C22335FDD0B9F56B94B8187A /* Pods-MathEditor_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -109,7 +95,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2040F7C6A902C4AEF598B076 /* libPods-MathEditor.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -120,7 +105,7 @@ 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */, 6003F592195388D20070C39A /* UIKit.framework in Frameworks */, 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */, - 1385A98064E559BF2F757304 /* libPods-MathEditor_Example.a in Frameworks */, + 862D28DD2E2D4C4400F9B6FE /* MathEditor in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -131,7 +116,6 @@ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */, 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */, 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */, - 243FB02EE755A1D41861623B /* libPods-MathEditor_Tests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -199,19 +183,6 @@ path = ../MathKeyboardResources; sourceTree = ""; }; - 4D5F9F41E950A502FB2B3BEA /* Pods */ = { - isa = PBXGroup; - children = ( - 1377C04C01CE56EB11AB5A1B /* Pods-MathEditor.debug.xcconfig */, - 8A93C35286D2F42BBFFF6281 /* Pods-MathEditor.release.xcconfig */, - C22335FDD0B9F56B94B8187A /* Pods-MathEditor_Example.debug.xcconfig */, - 7A378091F23FA26A8EEA2F82 /* Pods-MathEditor_Example.release.xcconfig */, - AA22ED6294D4E5576093F8A8 /* Pods-MathEditor_Tests.debug.xcconfig */, - A4EC35D984BA58883006D2C3 /* Pods-MathEditor_Tests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; 6003F581195388D10070C39A = { isa = PBXGroup; children = ( @@ -221,7 +192,6 @@ 490BE5571CE695CC00AE31A0 /* mathEditor */, 6003F58C195388D20070C39A /* Frameworks */, 6003F58B195388D20070C39A /* Products */, - 4D5F9F41E950A502FB2B3BEA /* Pods */, ); sourceTree = ""; }; @@ -242,12 +212,6 @@ 6003F58F195388D20070C39A /* CoreGraphics.framework */, 6003F591195388D20070C39A /* UIKit.framework */, 6003F5AF195388D20070C39A /* XCTest.framework */, - 5628D2459CDCF8D3064ABC73 /* libPods-iosMathEditor.a */, - 733BAF45E72F86EDFEAAAD61 /* libPods-iosMathEditor_Example.a */, - AEA5AA8AF2C91D3A7B9B05F9 /* libPods-iosMathEditor_Tests.a */, - 473720D7C87DFE272F052861 /* libPods-MathEditor.a */, - 8FF6761D7D7AC0EC9F0679C9 /* libPods-MathEditor_Example.a */, - 023479B03FDA39233191C9B7 /* libPods-MathEditor_Tests.a */, ); name = Frameworks; sourceTree = ""; @@ -313,11 +277,9 @@ isa = PBXNativeTarget; buildConfigurationList = 490BE55E1CE695CC00AE31A0 /* Build configuration list for PBXNativeTarget "MathEditor" */; buildPhases = ( - 48A9CBFCAB5894F9BFC552E4 /* [CP] Check Pods Manifest.lock */, 490BE5521CE695CC00AE31A0 /* Sources */, 490BE5531CE695CC00AE31A0 /* Frameworks */, 490BE5541CE695CC00AE31A0 /* CopyFiles */, - 4C3A2645A6B5FE31FA662BF1 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -332,12 +294,9 @@ isa = PBXNativeTarget; buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "MathEditor_Example" */; buildPhases = ( - A3E2D0D1A985C3FEA5864005 /* [CP] Check Pods Manifest.lock */, 6003F586195388D20070C39A /* Sources */, 6003F587195388D20070C39A /* Frameworks */, 6003F588195388D20070C39A /* Resources */, - D8A5CFAD5F09AAA428AB4D10 /* [CP] Embed Pods Frameworks */, - E202BD0EA2A01E97CAF04F2E /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -352,12 +311,9 @@ isa = PBXNativeTarget; buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "MathEditor_Tests" */; buildPhases = ( - 4B44B263AD3BCE039EE8DD71 /* [CP] Check Pods Manifest.lock */, 6003F5AA195388D20070C39A /* Sources */, 6003F5AB195388D20070C39A /* Frameworks */, 6003F5AC195388D20070C39A /* Resources */, - E5084554A0ECA0F3755B8CC7 /* [CP] Embed Pods Frameworks */, - 02495CB9EE6CCCBF5814B5C0 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -374,8 +330,9 @@ 6003F582195388D10070C39A /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; CLASSPREFIX = MT; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1640; ORGANIZATIONNAME = "Kostub Deshmukh"; TargetAttributes = { 490BE5551CE695CC00AE31A0 = { @@ -388,10 +345,14 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); mainGroup = 6003F581195388D10070C39A; + packageReferences = ( + 862D28DB2E2D4C4400F9B6FE /* XCLocalSwiftPackageReference "../MathEditor" */, + ); productRefGroup = 6003F58B195388D20070C39A /* Products */; projectDirPath = ""; projectRoot = ""; @@ -424,129 +385,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 02495CB9EE6CCCBF5814B5C0 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 48A9CBFCAB5894F9BFC552E4 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - 4B44B263AD3BCE039EE8DD71 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - 4C3A2645A6B5FE31FA662BF1 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor/Pods-MathEditor-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - A3E2D0D1A985C3FEA5864005 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - D8A5CFAD5F09AAA428AB4D10 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - E202BD0EA2A01E97CAF04F2E /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - E5084554A0ECA0F3755B8CC7 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 490BE5521CE695CC00AE31A0 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -603,13 +441,12 @@ /* Begin XCBuildConfiguration section */ 490BE55C1CE695CC00AE31A0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1377C04C01CE56EB11AB5A1B /* Pods-MathEditor.debug.xcconfig */; buildSettings = { CLANG_WARN_UNREACHABLE_CODE = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; OTHER_LDFLAGS = ( "$(inherited)", @@ -622,14 +459,13 @@ }; 490BE55D1CE695CC00AE31A0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8A93C35286D2F42BBFFF6281 /* Pods-MathEditor.release.xcconfig */; buildSettings = { CLANG_WARN_UNREACHABLE_CODE = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = ( "$(inherited)", @@ -644,23 +480,39 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -673,7 +525,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -684,29 +536,45 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -715,11 +583,11 @@ }; 6003F5C0195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C22335FDD0B9F56B94B8187A /* Pods-MathEditor_Example.debug.xcconfig */; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "MathEditorExample/MathEditor-Prefix.pch"; INFOPLIST_FILE = "$(SRCROOT)/MathEditorExample/iosMathEditor-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12; PRODUCT_BUNDLE_IDENTIFIER = MathChat.MathEditorExample; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; @@ -728,11 +596,11 @@ }; 6003F5C1195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7A378091F23FA26A8EEA2F82 /* Pods-MathEditor_Example.release.xcconfig */; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "MathEditorExample/MathEditor-Prefix.pch"; INFOPLIST_FILE = "$(SRCROOT)/MathEditorExample/iosMathEditor-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12; PRODUCT_BUNDLE_IDENTIFIER = MathChat.MathEditorExample; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; @@ -741,7 +609,6 @@ }; 6003F5C3195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AA22ED6294D4E5576093F8A8 /* Pods-MathEditor_Tests.debug.xcconfig */; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; @@ -765,7 +632,6 @@ }; 6003F5C4195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A4EC35D984BA58883006D2C3 /* Pods-MathEditor_Tests.release.xcconfig */; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; @@ -823,6 +689,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 862D28DB2E2D4C4400F9B6FE /* XCLocalSwiftPackageReference "../MathEditor" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../MathEditor; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 862D28DC2E2D4C4400F9B6FE /* MathEditor */ = { + isa = XCSwiftPackageProductDependency; + productName = MathEditor; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 6003F582195388D10070C39A /* Project object */; } diff --git a/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 6228eeb..919434a 100644 --- a/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/MathEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MathEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..2efe63d --- /dev/null +++ b/MathEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "3845228a2da848133f1c5b0fe02201c5631261188fc4a1a940a392278f808293", + "pins" : [ + { + "identity" : "iosmath", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maitbayev/iosMath.git", + "state" : { + "branch" : "master", + "revision" : "066ba2f8353782a644889efe9ceb884ea844180b" + } + } + ], + "version" : 3 +} diff --git a/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme b/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme index 6ca118a..ba87031 100644 --- a/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme +++ b/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme @@ -1,6 +1,6 @@ + + + + @@ -39,17 +48,6 @@ - - - - - - - - Date: Sun, 20 Jul 2025 21:51:07 +0100 Subject: [PATCH 006/133] Add SwiftUIExample --- .../project.pbxproj | 376 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/swiftpm/Package.resolved | 15 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 85 ++++ .../Assets.xcassets/Contents.json | 6 + .../ContentView.swift | 49 +++ .../MathEditorSwiftUIExample.entitlements | 10 + .../MathEditorSwiftUIExampleApp.swift | 12 + 9 files changed, 571 insertions(+) create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/Contents.json create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..9b98a5d --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj @@ -0,0 +1,376 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28F72E2D525900F9B6FE /* MathEditor */; }; + 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28FA2E2D527100F9B6FE /* MathEditor */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathEditorSwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 862D28E92E2D51E100F9B6FE /* MathEditorSwiftUIExample */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = MathEditorSwiftUIExample; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 862D28E42E2D51E100F9B6FE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */, + 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 862D28DE2E2D51E100F9B6FE = { + isa = PBXGroup; + children = ( + 862D28E92E2D51E100F9B6FE /* MathEditorSwiftUIExample */, + 862D28E82E2D51E100F9B6FE /* Products */, + ); + sourceTree = ""; + }; + 862D28E82E2D51E100F9B6FE /* Products */ = { + isa = PBXGroup; + children = ( + 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 862D28E62E2D51E100F9B6FE /* MathEditorSwiftUIExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 862D28F32E2D51E200F9B6FE /* Build configuration list for PBXNativeTarget "MathEditorSwiftUIExample" */; + buildPhases = ( + 862D28E32E2D51E100F9B6FE /* Sources */, + 862D28E42E2D51E100F9B6FE /* Frameworks */, + 862D28E52E2D51E100F9B6FE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 862D28E92E2D51E100F9B6FE /* MathEditorSwiftUIExample */, + ); + name = MathEditorSwiftUIExample; + packageProductDependencies = ( + 862D28F72E2D525900F9B6FE /* MathEditor */, + 862D28FA2E2D527100F9B6FE /* MathEditor */, + ); + productName = MathEditorSwiftUIExample; + productReference = 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 862D28DF2E2D51E100F9B6FE /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + 862D28E62E2D51E100F9B6FE = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = 862D28E22E2D51E100F9B6FE /* Build configuration list for PBXProject "MathEditorSwiftUIExample" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 862D28DE2E2D51E100F9B6FE; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 862D28E82E2D51E100F9B6FE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 862D28E62E2D51E100F9B6FE /* MathEditorSwiftUIExample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 862D28E52E2D51E100F9B6FE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 862D28E32E2D51E100F9B6FE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 862D28F12E2D51E200F9B6FE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = BT9948YGAL; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 862D28F22E2D51E200F9B6FE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = BT9948YGAL; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 862D28F42E2D51E200F9B6FE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = BT9948YGAL; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = matheditor.MathEditorSwiftUIExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.5; + }; + name = Debug; + }; + 862D28F52E2D51E200F9B6FE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = BT9948YGAL; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = matheditor.MathEditorSwiftUIExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.5; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 862D28E22E2D51E100F9B6FE /* Build configuration list for PBXProject "MathEditorSwiftUIExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 862D28F12E2D51E200F9B6FE /* Debug */, + 862D28F22E2D51E200F9B6FE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 862D28F32E2D51E200F9B6FE /* Build configuration list for PBXNativeTarget "MathEditorSwiftUIExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 862D28F42E2D51E200F9B6FE /* Debug */, + 862D28F52E2D51E200F9B6FE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../MathEditor; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 862D28F72E2D525900F9B6FE /* MathEditor */ = { + isa = XCSwiftPackageProductDependency; + productName = MathEditor; + }; + 862D28FA2E2D527100F9B6FE /* MathEditor */ = { + isa = XCSwiftPackageProductDependency; + productName = MathEditor; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 862D28DF2E2D51E100F9B6FE /* Project object */; +} diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..2efe63d --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "3845228a2da848133f1c5b0fe02201c5631261188fc4a1a940a392278f808293", + "pins" : [ + { + "identity" : "iosmath", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maitbayev/iosMath.git", + "state" : { + "branch" : "master", + "revision" : "066ba2f8353782a644889efe9ceb884ea844180b" + } + } + ], + "version" : 3 +} diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..ffdfe15 --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,85 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/Contents.json b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift new file mode 100644 index 0000000..8691bb7 --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -0,0 +1,49 @@ +// Copyright © 2025 Snap, Inc. All rights reserved. + +import SwiftUI +import MathEditor + +struct ContentView: View { + var body: some View { + MathEditorView() + .padding() + } +} + +#Preview { + ContentView() +} + +#if os(iOS) +struct MathEditorView : UIViewRepresentable { + typealias UIViewType = MTEditableMathLabel + + func makeUIView(context: Context) -> MTEditableMathLabel { + let mathLabel = MTEditableMathLabel() + mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance() + mathLabel.backgroundColor = .clear + return mathLabel + } + + func updateUIView(_ uiView: MTEditableMathLabel, context: Context) { + + } +} +#endif + +#if os(macOS) +struct MathEditorView : NSViewRepresentable { + typealias NSViewType = MTEditableMathLabel + + func makeNSView(context: Context) -> MTEditableMathLabel { + let mathLabel = MTEditableMathLabel() + mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance() + mathLabel.backgroundColor = .clear + return mathLabel + } + + func updateNSView(_ uiView: MTEditableMathLabel, context: Context) { + + } +} +#endif diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift new file mode 100644 index 0000000..7aa66b6 --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift @@ -0,0 +1,12 @@ +// Copyright © 2025 Snap, Inc. All rights reserved. + +import SwiftUI + +@main +struct MathEditorSwiftUIExampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} From 44f8830a4a2fa3cf7f7fbca9318a978384c52a0d Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Sun, 20 Jul 2025 23:02:44 +0100 Subject: [PATCH 007/133] Refactor out MathKeyboard --- .../MathEditorSwiftUIExample/ContentView.swift | 1 + Package.swift | 13 ++++++++++--- mathEditor/MathKeyboardResources | 1 - mathEditor/include/module.modulemap | 6 ------ .../back-arrow-disabled.imageset/Contents.json | 0 .../left arrow disabled 1x.png | Bin .../left arrow disabled 2x.png | Bin .../left arrow disabled 3x.png | Bin .../back-arrow.imageset/Contents.json | 0 .../back-arrow.imageset/left arrow 1x.png | Bin .../back-arrow.imageset/left arrow 2x.png | Bin .../back-arrow.imageset/left arrow 3x.png | Bin .../blue-button-highlighted.imageset/Contents.json | 0 .../blue-pressed.png | Bin .../front-arrow-disabled.imageset/Contents.json | 0 .../right arrow disabled 1x.png | Bin .../right arrow disabled 2x.png | Bin .../right arrow disabled 3x.png | Bin .../front-arrow.imageset/Contents.json | 0 .../front-arrow.imageset/right arrow 1x.png | Bin .../front-arrow.imageset/right arrow 2x.png | Bin .../front-arrow.imageset/right arrow 3x.png | Bin .../grey-button-disabled.imageset/Contents.json | 0 .../grey-button-disabled.png | Bin .../grey-button-highlighted.imageset/Contents.json | 0 .../keyboard-grey-pressed.png | Bin .../ipad-background.imageset/Contents.json | 0 .../ipad-background.imageset/ipad-keyboard1x.png | Bin .../ipad-background.imageset/ipad-keyboard2x.png | Bin .../iphone-background.imageset/Contents.json | 0 .../keyboard-background1x.png | Bin .../keyboard-background2x.png | Bin .../kb-dark-blue-pressed.imageset/Contents.json | 0 .../kb-dark-blue-pressed.png | Bin .../Contents.json | 0 .../kb-slide-button-pressed.png | Bin .../keyboard-slide-bg1.imageset/Contents.json | 0 .../keyboard-slide11x.png | Bin .../keyboard-slide12x.png | Bin .../keyboard-slide-bg2.imageset/Contents.json | 0 .../keyboard-slide21x.png | Bin .../keyboard-slide22x.png | Bin .../keyboard-slide-bg3.imageset/Contents.json | 0 .../keyboard-slide31x.png | Bin .../keyboard-slide32x.png | Bin .../Contents.json | 0 .../multiplication1x.png | Bin .../multiplication2x-ipad.png | Bin .../Contents.json | 0 .../multiplication1x-iphone.png | Bin .../multiplication2x-iphone.png | Bin .../num-button-disabled.imageset/Contents.json | 0 .../grey-button-disabled.png | Bin .../num-button-highlighted.imageset/Contents.json | 0 .../keyboard-num-pressed.png | Bin .../Contents.json | 0 .../keyboard-orange-pressed.png | Bin .../MathKeyboardResources}/MTKeyboard.xib | 0 .../MathKeyboardResources}/MTKeyboardTab2.xib | 0 .../MathKeyboardResources}/MTKeyboardTab3.xib | 0 .../MathKeyboardResources}/MTKeyboardTab4.xib | 0 .../MTMathKeyboardRootView.xib | 0 .../Backspace Small.imageset/Backspace Small.pdf | Bin .../Backspace Small.imageset/Contents.json | 0 .../Backspace.imageset/Backspace.pdf | Bin .../Backspace.imageset/Contents.json | 0 .../Exponent.imageset/Contents.json | 0 .../Exponent.imageset/Exponent.pdf | Bin .../Fraction.imageset/Contents.json | 0 .../Fraction.imageset/Fraction.pdf | Bin .../Functions Keyboard.imageset/Contents.json | 0 .../Functions Keyboard.pdf | Bin .../Functions Symbol.imageset/Contents.json | 0 .../Functions Symbol.imageset/Functions Symbol.pdf | Bin .../Keyboard Down.imageset/Contents.json | 0 .../Keyboard Down.imageset/Keyboard Down.pdf | Bin .../Keyboard-azure-pressed.imageset/Contents.json | 0 .../Keyboard-azure-pressed.pdf | Bin .../Keyboard-green-pressed.imageset/Contents.json | 0 .../Keyboard-green-pressed.pdf | Bin .../Keyboard-grey-pressed.imageset/Contents.json | 0 .../Keyboard-grey-pressed.pdf | Bin .../Keyboard-marine-pressed.imageset/Contents.json | 0 .../Keyboard-marine-pressed.pdf | Bin .../Keyboard-orange-pressed.imageset/Contents.json | 0 .../Keyboard-orange-pressed.pdf | Bin .../Letter Symbol.imageset/Contents.json | 0 .../Letter Symbol.imageset/Letter Symbol.pdf | Bin .../Letters Keyboard.imageset/Contents.json | 0 .../Letters Keyboard.imageset/Letters Keyboard.pdf | Bin .../Log Inverted.imageset/Contents.json | 0 .../Log Inverted.imageset/Log Inverted.pdf | Bin .../Log with base.imageset/Contents.json | 0 .../Log with base.imageset/Log with base.pdf | Bin .../Number Symbol.imageset/Contents.json | 0 .../Number Symbol.imageset/Number Symbol.pdf | Bin .../Numbers Keyboard.imageset/Contents.json | 0 .../Numbers Keyboard.imageset/Numbers Keyboard.pdf | Bin .../Operations Keyboard.imageset/Contents.json | 0 .../Operations Keyboard.pdf | Bin .../Operations Symbol.imageset/Contents.json | 0 .../Operations Symbol.pdf | Bin .../Shift.imageset/Contents.json | 0 .../Shift.imageset/Shift.pdf | Bin .../Sqrt Inverted.imageset/Contents.json | 0 .../Sqrt Inverted.imageset/Sqrt White Fixed.pdf | Bin .../Sqrt Power Inverted.imageset/Contents.json | 0 .../Sqrt Power Inverted.pdf | Bin .../Sqrt with Power.imageset/Contents.json | 0 .../Sqrt with Power.imageset/Sqrt with Power.pdf | Bin .../Sqrt.imageset/Contents.json | 0 .../Sqrt.imageset/Sqrt.pdf | Bin .../Subscript Inverted.imageset/Contents.json | 0 .../Subscript Inverted.pdf | Bin .../Subscript.imageset/Contents.json | 0 .../Subscript.imageset/Subscript.pdf | Bin .../Functions Symbol wbg.imageset/Contents.json | 0 .../Functions Symbol.pdf | Bin .../Letter Symbol wbg.imageset/Contents.json | 0 .../Letter Symbol wbg.imageset/Letter Symbol.pdf | Bin .../Numbers Symbol wbg.imageset/Contents.json | 0 .../Numbers Symbol wbg.imageset/Numbers Symbol.pdf | Bin .../Operations Symbol wbg.imageset/Contents.json | 0 .../Operations Symbol.pdf | Bin .../lmroman10-bolditalic.otf | Bin mathKeyboard/include/module.modulemap | 5 +++++ {mathEditor => mathKeyboard}/keyboard/MTKeyboard.h | 0 {mathEditor => mathKeyboard}/keyboard/MTKeyboard.m | 3 +-- .../keyboard/MTMathKeyboardRootView.h | 0 .../keyboard/MTMathKeyboardRootView.m | 3 --- 130 files changed, 17 insertions(+), 15 deletions(-) delete mode 120000 mathEditor/MathKeyboardResources delete mode 100644 mathEditor/include/module.modulemap rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/MTKeyboard.xib (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/MTKeyboardTab2.xib (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/MTKeyboardTab3.xib (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/MTKeyboardTab4.xib (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/MTMathKeyboardRootView.xib (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf (100%) rename {MathKeyboardResources => mathKeyboard/MathKeyboardResources}/lmroman10-bolditalic.otf (100%) create mode 100644 mathKeyboard/include/module.modulemap rename {mathEditor => mathKeyboard}/keyboard/MTKeyboard.h (100%) rename {mathEditor => mathKeyboard}/keyboard/MTKeyboard.m (98%) rename {mathEditor => mathKeyboard}/keyboard/MTMathKeyboardRootView.h (100%) rename {mathEditor => mathKeyboard}/keyboard/MTMathKeyboardRootView.m (97%) diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index 8691bb7..01748bf 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -2,6 +2,7 @@ import SwiftUI import MathEditor +import MathKeyboard struct ContentView: View { var body: some View { diff --git a/Package.swift b/Package.swift index 145c18c..860e8a3 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( products: [ .library( name: "MathEditor", - targets: ["MathEditor"]) + targets: ["MathEditor", "MathKeyboard"]) ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") @@ -19,13 +19,20 @@ let package = Package( name: "MathEditor", dependencies: [.product(name: "iosMath", package: "iosMath")], path: "./mathEditor", - resources: [.process("MathKeyboardResources")], cSettings: [ .headerSearchPath("./editor"), - .headerSearchPath("./keyboard"), .headerSearchPath("./internal"), ] ), + .target( + name: "MathKeyboard", + dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditor"], + path: "./mathKeyboard", + resources: [.process("MathKeyboardResources")], + cSettings: [ + .headerSearchPath("./keyboard"), + ] + ), .testTarget( name: "MathEditorTests", dependencies: ["MathEditor"], diff --git a/mathEditor/MathKeyboardResources b/mathEditor/MathKeyboardResources deleted file mode 120000 index 4b9ef22..0000000 --- a/mathEditor/MathKeyboardResources +++ /dev/null @@ -1 +0,0 @@ -../MathKeyboardResources \ No newline at end of file diff --git a/mathEditor/include/module.modulemap b/mathEditor/include/module.modulemap deleted file mode 100644 index a4d7a86..0000000 --- a/mathEditor/include/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -module MathEditor { - header "../editor/MTEditableMathLabel.h" - header "../keyboard/MTMathKeyboardRootView.h" - - export * -} diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png b/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png similarity index 100% rename from MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png rename to mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png diff --git a/MathKeyboardResources/MTKeyboard.xib b/mathKeyboard/MathKeyboardResources/MTKeyboard.xib similarity index 100% rename from MathKeyboardResources/MTKeyboard.xib rename to mathKeyboard/MathKeyboardResources/MTKeyboard.xib diff --git a/MathKeyboardResources/MTKeyboardTab2.xib b/mathKeyboard/MathKeyboardResources/MTKeyboardTab2.xib similarity index 100% rename from MathKeyboardResources/MTKeyboardTab2.xib rename to mathKeyboard/MathKeyboardResources/MTKeyboardTab2.xib diff --git a/MathKeyboardResources/MTKeyboardTab3.xib b/mathKeyboard/MathKeyboardResources/MTKeyboardTab3.xib similarity index 100% rename from MathKeyboardResources/MTKeyboardTab3.xib rename to mathKeyboard/MathKeyboardResources/MTKeyboardTab3.xib diff --git a/MathKeyboardResources/MTKeyboardTab4.xib b/mathKeyboard/MathKeyboardResources/MTKeyboardTab4.xib similarity index 100% rename from MathKeyboardResources/MTKeyboardTab4.xib rename to mathKeyboard/MathKeyboardResources/MTKeyboardTab4.xib diff --git a/MathKeyboardResources/MTMathKeyboardRootView.xib b/mathKeyboard/MathKeyboardResources/MTMathKeyboardRootView.xib similarity index 100% rename from MathKeyboardResources/MTMathKeyboardRootView.xib rename to mathKeyboard/MathKeyboardResources/MTMathKeyboardRootView.xib diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf b/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf similarity index 100% rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf rename to mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf b/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf similarity index 100% rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf rename to mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf diff --git a/MathKeyboardResources/lmroman10-bolditalic.otf b/mathKeyboard/MathKeyboardResources/lmroman10-bolditalic.otf similarity index 100% rename from MathKeyboardResources/lmroman10-bolditalic.otf rename to mathKeyboard/MathKeyboardResources/lmroman10-bolditalic.otf diff --git a/mathKeyboard/include/module.modulemap b/mathKeyboard/include/module.modulemap new file mode 100644 index 0000000..c1a74ac --- /dev/null +++ b/mathKeyboard/include/module.modulemap @@ -0,0 +1,5 @@ +module MathKeyboard { + header "../keyboard/MTMathKeyboardRootView.h" + + export * +} diff --git a/mathEditor/keyboard/MTKeyboard.h b/mathKeyboard/keyboard/MTKeyboard.h similarity index 100% rename from mathEditor/keyboard/MTKeyboard.h rename to mathKeyboard/keyboard/MTKeyboard.h diff --git a/mathEditor/keyboard/MTKeyboard.m b/mathKeyboard/keyboard/MTKeyboard.m similarity index 98% rename from mathEditor/keyboard/MTKeyboard.m rename to mathKeyboard/keyboard/MTKeyboard.m index 7a4b5fa..fd0201a 100644 --- a/mathEditor/keyboard/MTKeyboard.m +++ b/mathKeyboard/keyboard/MTKeyboard.m @@ -9,7 +9,6 @@ // #import "MTKeyboard.h" -#import "MTMathKeyboardRootView.h" #import "MTFontManager.h" #import "MTMathAtomFactory.h" @@ -37,7 +36,7 @@ - (NSString*) registerAndGetFontName static dispatch_once_t once_token; dispatch_once(&once_token, ^{ - NSBundle *bundle = [MTMathKeyboardRootView getMathKeyboardResourcesBundle]; + NSBundle *bundle = SWIFTPM_MODULE_BUNDLE; NSString* fontPath = [bundle pathForResource:@"lmroman10-bolditalic" ofType:@"otf"]; CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename([fontPath UTF8String]); CGFontRef myFont = CGFontCreateWithDataProvider(fontDataProvider); diff --git a/mathEditor/keyboard/MTMathKeyboardRootView.h b/mathKeyboard/keyboard/MTMathKeyboardRootView.h similarity index 100% rename from mathEditor/keyboard/MTMathKeyboardRootView.h rename to mathKeyboard/keyboard/MTMathKeyboardRootView.h diff --git a/mathEditor/keyboard/MTMathKeyboardRootView.m b/mathKeyboard/keyboard/MTMathKeyboardRootView.m similarity index 97% rename from mathEditor/keyboard/MTMathKeyboardRootView.m rename to mathKeyboard/keyboard/MTMathKeyboardRootView.m index 06696d0..39650f2 100644 --- a/mathEditor/keyboard/MTMathKeyboardRootView.m +++ b/mathKeyboard/keyboard/MTMathKeyboardRootView.m @@ -46,10 +46,7 @@ +(instancetype)sharedInstance { // Gets the math keyboard resources bundle +(NSBundle *)getMathKeyboardResourcesBundle { -#ifdef SWIFTPM_MODULE_BUNDLE return SWIFTPM_MODULE_BUNDLE; -#endif - return [NSBundle bundleWithURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"MTKeyboardResources" withExtension:@"bundle"]]; } -(void)awakeFromNib From 3fadb236f7ba5b4de2a6f3d818dcf8661506b298 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Mon, 21 Jul 2025 00:44:42 +0100 Subject: [PATCH 008/133] Support MacOS as no-op --- Package.swift | 7 +++++-- mathEditor/editor/MTEditableMathLabel.h | 4 ++++ mathEditor/editor/MTEditableMathLabel.m | 4 ++++ mathEditor/internal/MTCaretView.h | 6 +++--- mathEditor/internal/MTCaretView.m | 6 +++++- mathEditor/internal/MTDisplay+Editing.h | 3 +++ mathEditor/internal/MTDisplay+Editing.m | 4 ++++ 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Package.swift b/Package.swift index 860e8a3..bba9576 100644 --- a/Package.swift +++ b/Package.swift @@ -5,11 +5,14 @@ import PackageDescription let package = Package( name: "MathEditor", defaultLocalization: "en", - platforms: [.iOS(.v13)], + platforms: [.iOS(.v13), .macOS(.v11)], products: [ .library( name: "MathEditor", - targets: ["MathEditor", "MathKeyboard"]) + targets: ["MathEditor"]), + .library( + name: "MathKeyboard", + targets: ["MathKeyboard"]) ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index cf39bbd..796e419 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -8,6 +8,8 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import #include "MTMathList.h" @@ -89,3 +91,5 @@ - (CGSize) mathDisplaySize; @end + +#endif diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 0d732f2..48d30d2 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -8,6 +8,8 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import #import "MTEditableMathLabel.h" @@ -1043,3 +1045,5 @@ - (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition } @end + +#endif diff --git a/mathEditor/internal/MTCaretView.h b/mathEditor/internal/MTCaretView.h index 1c6bab8..09beae2 100644 --- a/mathEditor/internal/MTCaretView.h +++ b/mathEditor/internal/MTCaretView.h @@ -8,13 +8,13 @@ // MIT license. See the LICENSE file for details. // -#import +#import "MTConfig.h" @class MTEditableMathLabel; -@interface MTCaretView : UIView +@interface MTCaretView : MTView -@property (nonatomic) UIColor* caretColor; +@property (nonatomic) MTColor* caretColor; - (id) initWithEditor:(MTEditableMathLabel*)label; diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index 04e9f89..a03b4ca 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -8,8 +8,11 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import "MTCaretView.h" #import "MTEditableMathLabel.h" +#import "MTConfig.h" static const NSTimeInterval InitialBlinkDelay = 0.7; static const NSTimeInterval BlinkRate = 0.5; @@ -27,7 +30,7 @@ static NSInteger getCaretHeight() { return kCaretAscent + kCaretDescent; } -@interface MTCaretHandle : UIView +@interface MTCaretHandle : MTView @property (nonatomic, weak) MTEditableMathLabel* label; @@ -220,3 +223,4 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{ @end +#endif diff --git a/mathEditor/internal/MTDisplay+Editing.h b/mathEditor/internal/MTDisplay+Editing.h index 437232a..1d0ccde 100644 --- a/mathEditor/internal/MTDisplay+Editing.h +++ b/mathEditor/internal/MTDisplay+Editing.h @@ -8,6 +8,8 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import "MTMathListDisplay.h" #import "MTMathListIndex.h" @@ -27,3 +29,4 @@ @end +#endif diff --git a/mathEditor/internal/MTDisplay+Editing.m b/mathEditor/internal/MTDisplay+Editing.m index bc719e4..ec9d72b 100644 --- a/mathEditor/internal/MTDisplay+Editing.m +++ b/mathEditor/internal/MTDisplay+Editing.m @@ -8,6 +8,8 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import #import "MTDisplay+Editing.h" @@ -594,3 +596,5 @@ - (void)highlightWithColor:(UIColor *)color } @end + +#endif From 550b9056aea094c5cf67db0472ae1718c30dff0d Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Mon, 21 Jul 2025 10:48:04 +0100 Subject: [PATCH 009/133] MTDisplay+Editing on MacOS --- mathEditor/internal/MTDisplay+Editing.h | 7 ++--- mathEditor/internal/MTDisplay+Editing.m | 40 +++++++++++-------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/mathEditor/internal/MTDisplay+Editing.h b/mathEditor/internal/MTDisplay+Editing.h index 1d0ccde..7af2631 100644 --- a/mathEditor/internal/MTDisplay+Editing.h +++ b/mathEditor/internal/MTDisplay+Editing.h @@ -8,8 +8,6 @@ // MIT license. See the LICENSE file for details. // -#if TARGET_OS_IPHONE - #import "MTMathListDisplay.h" #import "MTMathListIndex.h" @@ -22,11 +20,10 @@ - (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; // Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color; +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; // Highlight the entire display with the given color -- (void) highlightWithColor:(UIColor*) color; +- (void) highlightWithColor:(MTColor*) color; @end -#endif diff --git a/mathEditor/internal/MTDisplay+Editing.m b/mathEditor/internal/MTDisplay+Editing.m index ec9d72b..a60ef5b 100644 --- a/mathEditor/internal/MTDisplay+Editing.m +++ b/mathEditor/internal/MTDisplay+Editing.m @@ -8,8 +8,6 @@ // MIT license. See the LICENSE file for details. // -#if TARGET_OS_IPHONE - #import #import "MTDisplay+Editing.h" @@ -95,11 +93,11 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index return kInvalidPosition; } -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color { } -- (void)highlightWithColor:(UIColor *)color +- (void)highlightWithColor:(MTColor *)color { } @end @@ -115,9 +113,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; // Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color; +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; -- (void)highlightWithColor:(UIColor *)color; +- (void)highlightWithColor:(MTColor *)color; @end @@ -158,7 +156,7 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index } -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color { assert(NSLocationInRange(index.atomIndex, self.range)); assert(index.subIndexType == kMTSubIndexTypeNone || index.subIndexType == kMTSubIndexTypeNucleus); @@ -175,7 +173,7 @@ - (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)colo self.attributedString = attrStr; } -- (void)highlightWithColor:(UIColor *)color +- (void)highlightWithColor:(MTColor *)color { NSMutableAttributedString* attrStr = self.attributedString.mutableCopy; [attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[color CGColor] @@ -224,9 +222,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; // Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color; +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; -- (void)highlightWithColor:(UIColor *)color; +- (void)highlightWithColor:(MTColor *)color; - (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type; @@ -263,13 +261,13 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index return CGPointMake(self.position.x, self.position.y); } -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color { assert(index.subIndexType == kMTSubIndexTypeNone); [self highlightWithColor:color]; } -- (void)highlightWithColor:(UIColor *)color +- (void)highlightWithColor:(MTColor *)color { [self.numerator highlightWithColor:color]; [self.denominator highlightWithColor:color]; @@ -308,9 +306,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; // Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color; +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; -- (void)highlightWithColor:(UIColor *)color; +- (void)highlightWithColor:(MTColor *)color; - (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type; @@ -350,13 +348,13 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index return CGPointMake(self.position.x, self.position.y); } -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color { assert(index.subIndexType == kMTSubIndexTypeNone); [self highlightWithColor:color]; } -- (void)highlightWithColor:(UIColor *)color +- (void)highlightWithColor:(MTColor *)color { [self.radicand highlightWithColor:color]; } @@ -395,9 +393,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; // Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color; +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; -- (void)highlightWithColor:(UIColor *)color; +- (void)highlightWithColor:(MTColor *)color; @end @@ -572,7 +570,7 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index } -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color { if (!index) { return; @@ -588,7 +586,7 @@ - (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)colo } } -- (void)highlightWithColor:(UIColor *)color +- (void)highlightWithColor:(MTColor *)color { for (MTDisplay* atom in self.subDisplays) { [atom highlightWithColor:color]; @@ -596,5 +594,3 @@ - (void)highlightWithColor:(UIColor *)color } @end - -#endif From 7a1f7add08a5297483bbf0a0a517a6905e3a4368 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 19 Mar 2026 22:48:21 +0000 Subject: [PATCH 010/133] keyboard compiles under macos but no-op --- .../project.pbxproj | 16 ++++++++++++++++ .../MathEditorSwiftUIExample/ContentView.swift | 18 +++++------------- mathKeyboard/keyboard/MTKeyboard.h | 4 ++++ mathKeyboard/keyboard/MTKeyboard.m | 4 ++++ mathKeyboard/keyboard/MTMathKeyboardRootView.h | 5 ++++- mathKeyboard/keyboard/MTMathKeyboardRootView.m | 4 ++++ 6 files changed, 37 insertions(+), 14 deletions(-) diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj index 9b98a5d..533b729 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28F72E2D525900F9B6FE /* MathEditor */; }; 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28FA2E2D527100F9B6FE /* MathEditor */; }; + C1489ED52F6CB33A00AB2993 /* MathKeyboard in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = C1489ED42F6CB33A00AB2993 /* MathKeyboard */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -28,6 +29,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C1489ED52F6CB33A00AB2993 /* MathKeyboard in Frameworks */, 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */, 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */, ); @@ -40,6 +42,7 @@ isa = PBXGroup; children = ( 862D28E92E2D51E100F9B6FE /* MathEditorSwiftUIExample */, + C14D8B922F6CB11E003F79FF /* Frameworks */, 862D28E82E2D51E100F9B6FE /* Products */, ); sourceTree = ""; @@ -52,6 +55,13 @@ name = Products; sourceTree = ""; }; + C14D8B922F6CB11E003F79FF /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -74,6 +84,7 @@ packageProductDependencies = ( 862D28F72E2D525900F9B6FE /* MathEditor */, 862D28FA2E2D527100F9B6FE /* MathEditor */, + C1489ED42F6CB33A00AB2993 /* MathKeyboard */, ); productName = MathEditorSwiftUIExample; productReference = 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */; @@ -370,6 +381,11 @@ isa = XCSwiftPackageProductDependency; productName = MathEditor; }; + C1489ED42F6CB33A00AB2993 /* MathKeyboard */ = { + isa = XCSwiftPackageProductDependency; + package = 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */; + productName = MathKeyboard; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 862D28DF2E2D51E100F9B6FE /* Project object */; diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index 01748bf..d211e12 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -21,8 +21,8 @@ struct MathEditorView : UIViewRepresentable { func makeUIView(context: Context) -> MTEditableMathLabel { let mathLabel = MTEditableMathLabel() - mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance() mathLabel.backgroundColor = .clear + mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance(); return mathLabel } @@ -33,18 +33,10 @@ struct MathEditorView : UIViewRepresentable { #endif #if os(macOS) -struct MathEditorView : NSViewRepresentable { - typealias NSViewType = MTEditableMathLabel - - func makeNSView(context: Context) -> MTEditableMathLabel { - let mathLabel = MTEditableMathLabel() - mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance() - mathLabel.backgroundColor = .clear - return mathLabel - } - - func updateNSView(_ uiView: MTEditableMathLabel, context: Context) { - +struct MathEditorView: View { + var body: some View { + Text("MathEditor is not wired up for macOS in this example yet.") + .frame(maxWidth: .infinity, maxHeight: .infinity) } } #endif diff --git a/mathKeyboard/keyboard/MTKeyboard.h b/mathKeyboard/keyboard/MTKeyboard.h index f96609d..727bb95 100644 --- a/mathKeyboard/keyboard/MTKeyboard.h +++ b/mathKeyboard/keyboard/MTKeyboard.h @@ -8,6 +8,8 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import #import "MTEditableMathLabel.h" @@ -63,3 +65,5 @@ - (void) setRadicalState:(BOOL) highlighted; @end + +#endif diff --git a/mathKeyboard/keyboard/MTKeyboard.m b/mathKeyboard/keyboard/MTKeyboard.m index fd0201a..6c765d3 100644 --- a/mathKeyboard/keyboard/MTKeyboard.m +++ b/mathKeyboard/keyboard/MTKeyboard.m @@ -8,6 +8,8 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import "MTKeyboard.h" #import "MTFontManager.h" #import "MTMathAtomFactory.h" @@ -272,3 +274,5 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event } @end + +#endif diff --git a/mathKeyboard/keyboard/MTMathKeyboardRootView.h b/mathKeyboard/keyboard/MTMathKeyboardRootView.h index e3cae99..3c3dc62 100644 --- a/mathKeyboard/keyboard/MTMathKeyboardRootView.h +++ b/mathKeyboard/keyboard/MTMathKeyboardRootView.h @@ -9,8 +9,9 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import -#import #import "MTKeyboard.h" #import "MTEditableMathLabel.h" @@ -42,3 +43,5 @@ @property (nonatomic) BOOL radicalHighlighted; @end + +#endif diff --git a/mathKeyboard/keyboard/MTMathKeyboardRootView.m b/mathKeyboard/keyboard/MTMathKeyboardRootView.m index 39650f2..b316b8e 100644 --- a/mathKeyboard/keyboard/MTMathKeyboardRootView.m +++ b/mathKeyboard/keyboard/MTMathKeyboardRootView.m @@ -9,6 +9,8 @@ // MIT license. See the LICENSE file for details. // +#if TARGET_OS_IPHONE + #import "MTMathKeyboardRootView.h" static NSInteger const DEFAULT_KEYBOARD = 0; @@ -227,3 +229,5 @@ - (void)finishedEditing:(UIView *)label } @end + +#endif From 56505164930fd0e18994f2683dc3896f1780dc32 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 19 Mar 2026 22:51:42 +0000 Subject: [PATCH 011/133] Replace UIColor with MTColor --- .../MathEditorSwiftUIExample/ContentView.swift | 4 +++- mathEditor/editor/MTEditableMathLabel.h | 7 ++++--- mathEditor/editor/MTEditableMathLabel.m | 14 +++++++------- mathEditor/internal/MTCaretView.m | 8 ++++---- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index d211e12..302b44b 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -2,7 +2,7 @@ import SwiftUI import MathEditor -import MathKeyboard + struct ContentView: View { var body: some View { @@ -16,6 +16,8 @@ struct ContentView: View { } #if os(iOS) +import MathKeyboard + struct MathEditorView : UIViewRepresentable { typealias UIViewType = MTEditableMathLabel diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index 796e419..ddf54dd 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -11,6 +11,7 @@ #if TARGET_OS_IPHONE #import +#import "MTConfig.h" #include "MTMathList.h" @class MTEditableMathLabel; @@ -67,9 +68,9 @@ @interface MTEditableMathLabel : UIView @property (nonatomic) MTMathList* mathList; -@property (nonatomic) UIColor* highlightColor; -@property (nonatomic) UIColor* textColor; -@property (nonatomic) UIColor* caretColor; +@property (nonatomic) MTColor* highlightColor; +@property (nonatomic) MTColor* textColor; +@property (nonatomic) MTColor* caretColor; @property (nonatomic) UIImageView* cancelImage; @property (nonatomic, weak) id delegate; diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 48d30d2..45cdad5 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -94,10 +94,10 @@ - (void) initialize _flipTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, -1.0), transform); _caretView = [[MTCaretView alloc] initWithEditor:self]; - _caretView.caretColor = [UIColor colorWithWhite:0.1 alpha:1.0]; + _caretView.caretColor = [MTColor colorWithWhite:0.1 alpha:1.0]; _indicesToHighlight = [NSMutableArray array]; - _highlightColor = [UIColor colorWithRed:0.8 green:0 blue:0.0 alpha:1.0]; + _highlightColor = [MTColor colorWithRed:0.8 green:0 blue:0.0 alpha:1.0]; [self bringSubviewToFront:self.cancelImage]; // start with an empty math list @@ -119,27 +119,27 @@ -(void)layoutSubviews [self insertionPointChanged]; } -- (void)setTextColor:(UIColor *)textColor +- (void)setTextColor:(MTColor *)textColor { self.label.textColor = textColor; } -- (UIColor*)textColor +- (MTColor*)textColor { return self.label.textColor; } -- (void)setCaretColor:(UIColor *)caretColor +- (void)setCaretColor:(MTColor *)caretColor { _caretView.caretColor = caretColor; } -- (UIColor *)caretColor +- (MTColor *)caretColor { return _caretView.caretColor; } -- (void)setBackgroundColor:(UIColor *)backgroundColor +- (void)setBackgroundColor:(MTColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; self.label.backgroundColor = backgroundColor; diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index a03b4ca..ba16c8c 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -38,7 +38,7 @@ @interface MTCaretHandle : MTView @implementation MTCaretHandle { UIBezierPath* _path; - UIColor* _color; + MTColor* _color; } - (id) initWithFrame:(CGRect)frame @@ -74,7 +74,7 @@ - (void)drawRect:(CGRect)rect [_path fill]; } -- (void) setColor:(UIColor*) color +- (void) setColor:(MTColor*) color { _color = [color colorWithAlphaComponent:0.6]; } @@ -136,7 +136,7 @@ - (id)initWithEditor:(MTEditableMathLabel*)label _blinker.backgroundColor = self.caretColor; [self addSubview:_blinker]; _handle = [[MTCaretHandle alloc] initWithFrame:CGRectMake(0, 0, kCaretHandleWidth * _scale, kCaretHandleHeight *_scale)]; - _handle.backgroundColor = [UIColor clearColor]; + _handle.backgroundColor = [MTColor clearColor]; _handle.hidden = YES; _handle.label = label; [self addSubview:_handle]; @@ -205,7 +205,7 @@ - (void)dealloc [_blinkTimer invalidate]; } -- (void)setCaretColor:(UIColor *)caretColor +- (void)setCaretColor:(MTColor *)caretColor { _caretColor = caretColor; _handle.color = caretColor; From 82a21610c257c911ce8ab5a8367107b8a7bc32cf Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 19 Mar 2026 23:04:07 +0000 Subject: [PATCH 012/133] MTKeyInput protocol --- mathEditor/editor/MTEditableMathLabel.h | 9 +++---- mathEditor/editor/MTKeyInput.h | 24 +++++++++++++++++++ mathEditor/include/MTKeyInput.h | 1 + mathKeyboard/keyboard/MTKeyboard.h | 2 +- .../keyboard/MTMathKeyboardRootView.m | 4 ++-- 5 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 mathEditor/editor/MTKeyInput.h create mode 120000 mathEditor/include/MTKeyInput.h diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index ddf54dd..d085f9e 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -12,6 +12,7 @@ #import #import "MTConfig.h" +#import "MTKeyInput.h" #include "MTMathList.h" @class MTEditableMathLabel; @@ -53,19 +54,19 @@ this protocol. This protocol informs the keyboard when a particular `MTEditableMathUILabel` is being edited. - The keyboard should use this information to send `UIKeyInput` messages to the label. + The keyboard should use this information to send `MTKeyInput` messages to the label. This protocol inherits from `MTMathKeyboardTraits`. */ @protocol MTMathKeyboard -- (void) startedEditing:(UIView*) label; -- (void) finishedEditing:(UIView*) label; +- (void) startedEditing:(MTView*) label; +- (void) finishedEditing:(MTView*) label; @end -@interface MTEditableMathLabel : UIView +@interface MTEditableMathLabel : MTView @property (nonatomic) MTMathList* mathList; @property (nonatomic) MTColor* highlightColor; diff --git a/mathEditor/editor/MTKeyInput.h b/mathEditor/editor/MTKeyInput.h new file mode 100644 index 0000000..9eab026 --- /dev/null +++ b/mathEditor/editor/MTKeyInput.h @@ -0,0 +1,24 @@ +// +// MTKeyInput.h +// +// Created for cross-platform key input abstraction. +// + +#import "MTConfig.h" + +#if TARGET_OS_IPHONE + +#import +#define MTKeyInput UIKeyInput + +#else + +@protocol MTKeyInput + +- (void)insertText:(NSString *)text; +- (void)deleteBackward; +- (BOOL)hasText; + +@end + +#endif diff --git a/mathEditor/include/MTKeyInput.h b/mathEditor/include/MTKeyInput.h new file mode 120000 index 0000000..ae5a7f9 --- /dev/null +++ b/mathEditor/include/MTKeyInput.h @@ -0,0 +1 @@ +../editor/MTKeyInput.h \ No newline at end of file diff --git a/mathKeyboard/keyboard/MTKeyboard.h b/mathKeyboard/keyboard/MTKeyboard.h index 727bb95..626e63e 100644 --- a/mathKeyboard/keyboard/MTKeyboard.h +++ b/mathKeyboard/keyboard/MTKeyboard.h @@ -27,7 +27,7 @@ @property (weak, nonatomic) IBOutlet UIButton *squareRootButton; @property (weak, nonatomic) IBOutlet UIButton *radicalButton; -@property (nonatomic, weak) UIView* textView; +@property (nonatomic, weak) MTView* textView; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *numbers; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *variables; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *operators; diff --git a/mathKeyboard/keyboard/MTMathKeyboardRootView.m b/mathKeyboard/keyboard/MTMathKeyboardRootView.m index b316b8e..d542560 100644 --- a/mathKeyboard/keyboard/MTMathKeyboardRootView.m +++ b/mathKeyboard/keyboard/MTMathKeyboardRootView.m @@ -214,14 +214,14 @@ - (void)setRadicalHighlighted:(BOOL)radicalHighlighted } } -- (void)startedEditing:(UIView *)label +- (void)startedEditing:(MTView *)label { for (MTKeyboard *keyboard in _keyboards) { keyboard.textView = label; } } -- (void)finishedEditing:(UIView *)label +- (void)finishedEditing:(MTView *)label { for (MTKeyboard *keyboard in _keyboards) { keyboard.textView = nil; From 87b55735726b7fd64a2c8c8b26d30301992e6e54 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 19 Mar 2026 23:15:53 +0000 Subject: [PATCH 013/133] Use MT* instead of UI* --- mathEditor/editor/MTEditableMathLabel.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index d085f9e..7f2b494 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -10,7 +10,6 @@ #if TARGET_OS_IPHONE -#import #import "MTConfig.h" #import "MTKeyInput.h" #include "MTMathList.h" @@ -75,9 +74,9 @@ @property (nonatomic) UIImageView* cancelImage; @property (nonatomic, weak) id delegate; -@property (nonatomic, weak) UIView* keyboard; +@property (nonatomic, weak) MTView* keyboard; @property (nonatomic) CGFloat fontSize; -@property (nonatomic) IBInspectable UIEdgeInsets contentInsets; +@property (nonatomic) MTEdgeInsets contentInsets; - (void) clear; From 35b70fe8f5b858c44b7f3673feffc1b131e18603 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 19 Mar 2026 23:42:40 +0000 Subject: [PATCH 014/133] Abstract MTCancelView --- mathEditor/editor/MTEditableMathLabel.h | 6 +-- mathEditor/editor/MTEditableMathLabel.m | 13 +++---- mathEditor/internal/MTCancelView.h | 17 +++++++++ mathEditor/internal/MTCancelView.m | 50 +++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 mathEditor/internal/MTCancelView.h create mode 100644 mathEditor/internal/MTCancelView.m diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index 7f2b494..bae650b 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -8,14 +8,13 @@ // MIT license. See the LICENSE file for details. // -#if TARGET_OS_IPHONE - #import "MTConfig.h" #import "MTKeyInput.h" #include "MTMathList.h" @class MTEditableMathLabel; @class MTMathListIndex; +@class MTCancelView; /** Delegate for the `MTEditableMathLabel`. All methods are optional. */ @protocol MTEditableMathLabelDelegate @@ -72,7 +71,7 @@ @property (nonatomic) MTColor* textColor; @property (nonatomic) MTColor* caretColor; -@property (nonatomic) UIImageView* cancelImage; +@property (nonatomic) MTCancelView* cancelImage; @property (nonatomic, weak) id delegate; @property (nonatomic, weak) MTView* keyboard; @property (nonatomic) CGFloat fontSize; @@ -93,4 +92,3 @@ @end -#endif diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 45cdad5..bbc0da0 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -16,6 +16,7 @@ #import "MTMathList.h" #import "MTMathUILabel.h" #import "MTMathAtomFactory.h" +#import "MTCancelView.h" #import "MTCaretView.h" #import "MTMathList+Editing.h" #import "MTDisplay+Editing.h" @@ -55,16 +56,13 @@ - (void)awakeFromNib - (void) createCancelImage { - self.cancelImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cross"]]; + if (self.cancelImage != nil) { + return; + } + self.cancelImage = [[MTCancelView alloc] initWithTarget:self action:@selector(clearTapped:)]; CGRect frame = CGRectMake(self.frame.size.width - 55, (self.frame.size.height - 45)/2, 45, 45); self.cancelImage.frame = frame; [self addSubview:self.cancelImage]; - - self.cancelImage.userInteractionEnabled = YES; - UITapGestureRecognizer *cancelRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clearTapped:)]; - [self.cancelImage addGestureRecognizer:cancelRecognizer]; - cancelRecognizer.delegate = nil; - self.cancelImage.hidden = YES; } - (void) initialize @@ -90,6 +88,7 @@ - (void) initialize label.userInteractionEnabled = NO; label.textAlignment = kMTTextAlignmentCenter; self.label = label; + // [self createCancelImage]; CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height); _flipTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, -1.0), transform); diff --git a/mathEditor/internal/MTCancelView.h b/mathEditor/internal/MTCancelView.h new file mode 100644 index 0000000..8939b61 --- /dev/null +++ b/mathEditor/internal/MTCancelView.h @@ -0,0 +1,17 @@ +// +// MTCancelView.h +// +// Created for the editable label clear affordance. +// + +#import "MTConfig.h" + +#if TARGET_OS_IPHONE + +@interface MTCancelView : MTView + +- (instancetype)initWithTarget:(id)target action:(SEL)action; + +@end + +#endif diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m new file mode 100644 index 0000000..0dc6f10 --- /dev/null +++ b/mathEditor/internal/MTCancelView.m @@ -0,0 +1,50 @@ +// +// MTCancelView.m +// +// Created for the editable label clear affordance. +// + +#if TARGET_OS_IPHONE + +#import "MTCancelView.h" + +@interface MTCancelView () + +@property (nonatomic, strong) UIImageView *imageView; + +@end + +@implementation MTCancelView + +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithFrame:CGRectZero]; + if (self) { + UIImage *image = [UIImage systemImageNamed:@"xmark.circle"]; + if (image != nil) { + image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + } + _imageView = [[UIImageView alloc] initWithImage:image]; + _imageView.translatesAutoresizingMaskIntoConstraints = NO; + _imageView.contentMode = UIViewContentModeScaleAspectFit; + _imageView.tintColor = [MTColor secondaryLabelColor]; + [self addSubview:_imageView]; + + [NSLayoutConstraint activateConstraints:@[ + [_imageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [_imageView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], + [_imageView.topAnchor constraintEqualToAnchor:self.topAnchor], + [_imageView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], + ]]; + + self.userInteractionEnabled = YES; + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:target action:action]; + [self addGestureRecognizer:tapRecognizer]; + self.hidden = YES; + } + return self; +} + +@end + +#endif From 03c49704c1ec8d930b21c53a4ae62da325c25663 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 19 Mar 2026 23:59:59 +0000 Subject: [PATCH 015/133] small update --- mathEditor/editor/MTEditableMathLabel.m | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index bbc0da0..0b10b69 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -59,7 +59,7 @@ - (void) createCancelImage if (self.cancelImage != nil) { return; } - self.cancelImage = [[MTCancelView alloc] initWithTarget:self action:@selector(clearTapped:)]; + self.cancelImage = [[MTCancelView alloc] initWithTarget:self action:@selector(clear)]; CGRect frame = CGRectMake(self.frame.size.width - 55, (self.frame.size.height - 45)/2, 45, 45); self.cancelImage.frame = frame; [self addSubview:self.cancelImage]; @@ -266,11 +266,6 @@ - (void)tap:(UITapGestureRecognizer *)tap } } -- (void)clearTapped:(UITapGestureRecognizer *)tap -{ - [self clear]; -} - - (void)clear { self.mathList = [MTMathList new]; From f7ffb1f855428223b6bc06b686ad778f1088a192 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Fri, 20 Mar 2026 00:09:41 +0000 Subject: [PATCH 016/133] abstract gesture recognizer --- mathEditor/editor/MTEditableMathLabel.m | 9 +++--- mathEditor/internal/MTCancelView.m | 3 +- mathEditor/internal/MTTapGestureRecognizer.h | 31 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 mathEditor/internal/MTTapGestureRecognizer.h diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 0b10b69..bbec8d2 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -18,6 +18,7 @@ #import "MTMathAtomFactory.h" #import "MTCancelView.h" #import "MTCaretView.h" +#import "MTTapGestureRecognizer.h" #import "MTMathList+Editing.h" #import "MTDisplay+Editing.h" @@ -27,7 +28,7 @@ @interface MTEditableMathLabel() @property (nonatomic) MTMathUILabel* label; -@property (nonatomic) UITapGestureRecognizer* tapGestureRecognizer; +@property (nonatomic) MTTapGestureRecognizer* tapGestureRecognizer; @end @@ -68,7 +69,7 @@ - (void) createCancelImage - (void) initialize { // Add tap gesture recognizer to let the user enter editing mode. - self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; + self.tapGestureRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; [self addGestureRecognizer:self.tapGestureRecognizer]; self.tapGestureRecognizer.delegate = self; @@ -249,7 +250,7 @@ - (void) startEditing /** Our tap gesture recognizer selector that enters editing mode, or if already in editing mode, updates the text insertion point. */ -- (void)tap:(UITapGestureRecognizer *)tap +- (void)tap:(MTTapGestureRecognizer *)tap { if (![self isFirstResponder]) { _insertionIndex = nil; @@ -257,7 +258,7 @@ - (void)tap:(UITapGestureRecognizer *)tap [self startEditing]; } else { // If already editing move the cursor and show handle - _insertionIndex = [self closestIndexToPoint:[tap locationInView:self]]; + _insertionIndex = [self closestIndexToPoint:MTTapGestureLocationInView(tap, self)]; if (_insertionIndex == nil) { _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count]; } diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m index 0dc6f10..906cca2 100644 --- a/mathEditor/internal/MTCancelView.m +++ b/mathEditor/internal/MTCancelView.m @@ -7,6 +7,7 @@ #if TARGET_OS_IPHONE #import "MTCancelView.h" +#import "MTTapGestureRecognizer.h" @interface MTCancelView () @@ -38,7 +39,7 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action ]]; self.userInteractionEnabled = YES; - UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:target action:action]; + MTTapGestureRecognizer *tapRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:target action:action]; [self addGestureRecognizer:tapRecognizer]; self.hidden = YES; } diff --git a/mathEditor/internal/MTTapGestureRecognizer.h b/mathEditor/internal/MTTapGestureRecognizer.h new file mode 100644 index 0000000..aa30a08 --- /dev/null +++ b/mathEditor/internal/MTTapGestureRecognizer.h @@ -0,0 +1,31 @@ +// +// MTTapGestureRecognizer.h +// +// Small cross-platform tap gesture abstraction. +// + +#import "MTConfig.h" + +#if TARGET_OS_IPHONE + +#import + +#define MTTapGestureRecognizer UITapGestureRecognizer + +static inline CGPoint MTTapGestureLocationInView(MTTapGestureRecognizer *gesture, MTView *view) +{ + return [gesture locationInView:view]; +} + +#else + +#import + +#define MTTapGestureRecognizer NSClickGestureRecognizer + +static inline CGPoint MTTapGestureLocationInView(MTTapGestureRecognizer *gesture, MTView *view) +{ + return [gesture locationInView:view]; +} + +#endif From 62db2a0685ba215d38b59137792c67bed423bca5 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Fri, 20 Mar 2026 00:11:14 +0000 Subject: [PATCH 017/133] MTCancelView on macos --- mathEditor/internal/MTCancelView.h | 6 +----- mathEditor/internal/MTCancelView.m | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/mathEditor/internal/MTCancelView.h b/mathEditor/internal/MTCancelView.h index 8939b61..8d243c6 100644 --- a/mathEditor/internal/MTCancelView.h +++ b/mathEditor/internal/MTCancelView.h @@ -4,14 +4,10 @@ // Created for the editable label clear affordance. // -#import "MTConfig.h" - -#if TARGET_OS_IPHONE +@import iosMath; @interface MTCancelView : MTView - (instancetype)initWithTarget:(id)target action:(SEL)action; @end - -#endif diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m index 906cca2..70ce8d3 100644 --- a/mathEditor/internal/MTCancelView.m +++ b/mathEditor/internal/MTCancelView.m @@ -4,14 +4,16 @@ // Created for the editable label clear affordance. // -#if TARGET_OS_IPHONE - #import "MTCancelView.h" #import "MTTapGestureRecognizer.h" @interface MTCancelView () +#if TARGET_OS_IPHONE @property (nonatomic, strong) UIImageView *imageView; +#else +@property (nonatomic, strong) NSImageView *imageView; +#endif @end @@ -21,6 +23,7 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action { self = [super initWithFrame:CGRectZero]; if (self) { +#if TARGET_OS_IPHONE UIImage *image = [UIImage systemImageNamed:@"xmark.circle"]; if (image != nil) { image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; @@ -29,6 +32,14 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action _imageView.translatesAutoresizingMaskIntoConstraints = NO; _imageView.contentMode = UIViewContentModeScaleAspectFit; _imageView.tintColor = [MTColor secondaryLabelColor]; +#else + NSImage *image = [NSImage imageWithSystemSymbolName:@"xmark.circle" accessibilityDescription:nil]; + _imageView = [[NSImageView alloc] initWithFrame:CGRectZero]; + _imageView.image = image; + _imageView.translatesAutoresizingMaskIntoConstraints = NO; + _imageView.imageScaling = NSImageScaleProportionallyUpOrDown; + _imageView.contentTintColor = [MTColor secondaryLabelColor]; +#endif [self addSubview:_imageView]; [NSLayoutConstraint activateConstraints:@[ @@ -38,7 +49,9 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action [_imageView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], ]]; +#if TARGET_OS_IPHONE self.userInteractionEnabled = YES; +#endif MTTapGestureRecognizer *tapRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:target action:action]; [self addGestureRecognizer:tapRecognizer]; self.hidden = YES; @@ -47,5 +60,3 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action } @end - -#endif From cc950f78fb695981c79250ef26e983bae06fc2ff Mon Sep 17 00:00:00 2001 From: Madiyar Date: Fri, 20 Mar 2026 00:11:42 +0000 Subject: [PATCH 018/133] nit --- mathEditor/internal/MTTapGestureRecognizer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathEditor/internal/MTTapGestureRecognizer.h b/mathEditor/internal/MTTapGestureRecognizer.h index aa30a08..c58b1df 100644 --- a/mathEditor/internal/MTTapGestureRecognizer.h +++ b/mathEditor/internal/MTTapGestureRecognizer.h @@ -4,7 +4,7 @@ // Small cross-platform tap gesture abstraction. // -#import "MTConfig.h" +@import iosMath; #if TARGET_OS_IPHONE From ef9ba3d251b8a7bdfb5c76fe8609a96f1c74164d Mon Sep 17 00:00:00 2001 From: Madiyar Date: Fri, 20 Mar 2026 00:19:10 +0000 Subject: [PATCH 019/133] Remove unused gesture delegate --- mathEditor/editor/MTEditableMathLabel.h | 3 +-- mathEditor/editor/MTEditableMathLabel.m | 28 +++++++++---------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index bae650b..57a0703 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -8,7 +8,7 @@ // MIT license. See the LICENSE file for details. // -#import "MTConfig.h" +@import iosMath; #import "MTKeyInput.h" #include "MTMathList.h" @@ -91,4 +91,3 @@ - (CGSize) mathDisplaySize; @end - diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index bbec8d2..b9284ba 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -25,13 +25,12 @@ #import "MTUnicode.h" #import "MTMathListBuilder.h" -@interface MTEditableMathLabel() +@interface MTEditableMathLabel() @property (nonatomic) MTMathUILabel* label; @property (nonatomic) MTTapGestureRecognizer* tapGestureRecognizer; @end - @implementation MTEditableMathLabel { MTCaretView* _caretView; MTMathListIndex* _insertionIndex; @@ -71,7 +70,6 @@ - (void) initialize // Add tap gesture recognizer to let the user enter editing mode. self.tapGestureRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; [self addGestureRecognizer:self.tapGestureRecognizer]; - self.tapGestureRecognizer.delegate = self; // Create our text storage. @@ -89,7 +87,7 @@ - (void) initialize label.userInteractionEnabled = NO; label.textAlignment = kMTTextAlignmentCenter; self.label = label; - // [self createCancelImage]; + [self createCancelImage]; CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height); _flipTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, -1.0), transform); @@ -158,12 +156,12 @@ - (CGFloat)fontSize return self.label.fontSize; } -- (void)setContentInsets:(UIEdgeInsets)contentInsets +- (void)setContentInsets:(MTEdgeInsets)contentInsets { self.label.contentInsets = contentInsets; } -- (UIEdgeInsets)contentInsets +- (MTEdgeInsets)contentInsets { return self.label.contentInsets; } @@ -228,17 +226,6 @@ - (BOOL)resignFirstResponder return val; } -/** - UIGestureRecognizerDelegate method. - Called to determine if we want to handle a given gesture. - */ -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch -{ - // If gesture touch occurs in our view, we want to handle it - return YES; - //return (touch.view == self); -} - - (void) startEditing { if (![self isFirstResponder]) { @@ -251,6 +238,11 @@ - (void) startEditing Our tap gesture recognizer selector that enters editing mode, or if already in editing mode, updates the text insertion point. */ - (void)tap:(MTTapGestureRecognizer *)tap +{ + [self handleTapAtPoint:MTTapGestureLocationInView(tap, self)]; +} + +- (void)handleTapAtPoint:(CGPoint)tapPoint { if (![self isFirstResponder]) { _insertionIndex = nil; @@ -258,7 +250,7 @@ - (void)tap:(MTTapGestureRecognizer *)tap [self startEditing]; } else { // If already editing move the cursor and show handle - _insertionIndex = [self closestIndexToPoint:MTTapGestureLocationInView(tap, self)]; + _insertionIndex = [self closestIndexToPoint:tapPoint]; if (_insertionIndex == nil) { _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count]; } From 57c8d0fce2c8146f5b58a93c4cbe13a9ba1c62bc Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 12:03:34 +0000 Subject: [PATCH 020/133] Isolate UITextInput methods --- .../editor/MTEditableMathLabel+UITextInput.h | 17 +++++++++++++++++ .../NSObject+MTEditableMathLabel_UITextInput.m | 12 ++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 mathEditor/editor/MTEditableMathLabel+UITextInput.h create mode 100644 mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m diff --git a/mathEditor/editor/MTEditableMathLabel+UITextInput.h b/mathEditor/editor/MTEditableMathLabel+UITextInput.h new file mode 100644 index 0000000..abc3f5f --- /dev/null +++ b/mathEditor/editor/MTEditableMathLabel+UITextInput.h @@ -0,0 +1,17 @@ +// +// NSObject+MTEditableMathLabel_UITextInput.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import +#import "MTEditableMathLabel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MTEditableMathLabel () + +@end + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m b/mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m new file mode 100644 index 0000000..59128c6 --- /dev/null +++ b/mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m @@ -0,0 +1,12 @@ +// +// NSObject+MTEditableMathLabel_UITextInput.m +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "NSObject+MTEditableMathLabel_UITextInput.h" + +@implementation NSObject (MTEditableMathLabel_UITextInput) + +@end From 0407e257db1bd8e21bf200d95f9d1168a3aaf8b1 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 12:05:31 +0000 Subject: [PATCH 021/133] Isolate UITextInput methods --- .../editor/MTEditableMathLabel+UITextInput.h | 17 -- .../editor/MTEditableMathLabel+UITextInput.m | 199 ++++++++++++++++++ mathEditor/editor/MTEditableMathLabel.m | 143 +------------ ...NSObject+MTEditableMathLabel_UITextInput.m | 12 -- 4 files changed, 200 insertions(+), 171 deletions(-) delete mode 100644 mathEditor/editor/MTEditableMathLabel+UITextInput.h create mode 100644 mathEditor/editor/MTEditableMathLabel+UITextInput.m delete mode 100644 mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m diff --git a/mathEditor/editor/MTEditableMathLabel+UITextInput.h b/mathEditor/editor/MTEditableMathLabel+UITextInput.h deleted file mode 100644 index abc3f5f..0000000 --- a/mathEditor/editor/MTEditableMathLabel+UITextInput.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// NSObject+MTEditableMathLabel_UITextInput.h -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import -#import "MTEditableMathLabel.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface MTEditableMathLabel () - -@end - -NS_ASSUME_NONNULL_END diff --git a/mathEditor/editor/MTEditableMathLabel+UITextInput.m b/mathEditor/editor/MTEditableMathLabel+UITextInput.m new file mode 100644 index 0000000..35c58d8 --- /dev/null +++ b/mathEditor/editor/MTEditableMathLabel+UITextInput.m @@ -0,0 +1,199 @@ +// +// NSObject+MTEditableMathLabel_UITextInput.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#if TARGET_OS_IPHONE + +#import +#import +#import "MTEditableMathLabel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MTEditableMathLabel () +@end + +@implementation MTEditableMathLabel (UITextInput) + +// These are blank just to get a UITextInput implementation, to fix the dictation button bug. +// Proposed fix from: http://stackoverflow.com/questions/20980898/work-around-for-dictation-custom-text-view-bug + +//@synthesize beginningOfDocument;@ +//@synthesize endOfDocument; +//@synthesize inputDelegate; +//@synthesize markedTextRange; +//@synthesize markedTextStyle; +//@synthesize selectedTextRange; +//@synthesize tokenizer; + +- (nullable UITextRange *)selectedTextRange { + return objc_getAssociatedObject(self, @selector(selectedTextRange)); +} +- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange { + objc_setAssociatedObject(self, @selector(selectedTextRange), selectedTextRange, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (nullable UITextRange *)markedTextRange { + return objc_getAssociatedObject(self, @selector(markedTextRange)); +} + +- (nullable NSDictionary *)markedTextStyle { + return objc_getAssociatedObject(self, @selector(markedTextStyle)); +} +- (void)setMarkedTextStyle:(nullable NSDictionary *)markedTextStyle { + objc_setAssociatedObject(self, @selector(markedTextStyle), markedTextStyle, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +- (UITextPosition *)beginningOfDocument { + return objc_getAssociatedObject(self, @selector(beginningOfDocument)); +} + +- (UITextPosition *)endOfDocument { + return objc_getAssociatedObject(self, @selector(endOfDocument)); +} + +- (id)tokenizer { + id tokenizer = objc_getAssociatedObject(self, @selector(tokenizer)); + if (!tokenizer) { + tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self]; + objc_setAssociatedObject(self, @selector(tokenizer), tokenizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return tokenizer; +} + +- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction +{ + return UITextWritingDirectionLeftToRight; +} + +- (CGRect)caretRectForPosition:(UITextPosition *)position +{ + return CGRectZero; +} + +- (void)unmarkText +{ + +} + +- (nullable UITextRange *)characterRangeAtPoint:(CGPoint)point +{ + return nil; +} +- (nullable UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction +{ + return nil; +} +- (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point +{ + return nil; +} +- (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range +{ + return nil; +} +- (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other +{ + return NSOrderedSame; +} +- (void)dictationRecognitionFailed +{ +} +- (void)dictationRecordingDidEnd +{ +} +- (CGRect)firstRectForRange:(UITextRange *)range +{ + return CGRectZero; +} + +- (CGRect)frameForDictationResultPlaceholder:(id)placeholder +{ + return CGRectZero; +} +- (void)insertDictationResult:(NSArray *)dictationResult +{ +} +- (id)insertDictationResultPlaceholder +{ + return nil; +} + +- (NSInteger)offsetFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition +{ + return 0; +} +- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset +{ + return nil; +} +- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset +{ + return nil; +} + +- (nullable UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction +{ + return nil; +} +- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult +{ +} +- (void)replaceRange:(UITextRange *)range withText:(NSString *)text +{ +} +- (NSArray *)selectionRectsForRange:(UITextRange *)range +{ + return nil; +} +- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range +{ +} +- (void)setMarkedText:(nullable NSString *)markedText selectedRange:(NSRange)selectedRange +{ +} + +- (nullable NSString *)textInRange:(UITextRange *)range +{ + return nil; +} +- (nullable UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition +{ + return nil; +} + +#pragma mark - UITextInputTraits + +- (UITextAutocapitalizationType)autocapitalizationType +{ + return UITextAutocapitalizationTypeNone; +} + +- (UITextAutocorrectionType)autocorrectionType +{ + return UITextAutocorrectionTypeNo; +} + +- (UIReturnKeyType)returnKeyType +{ + return UIReturnKeyDefault; +} + +- (UITextSpellCheckingType)spellCheckingType +{ + return UITextSpellCheckingTypeNo; +} + +- (UIKeyboardType)keyboardType +{ + return UIKeyboardTypeASCIICapable; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index b9284ba..72bb7e0 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -25,7 +25,7 @@ #import "MTUnicode.h" #import "MTMathListBuilder.h" -@interface MTEditableMathLabel() +@interface MTEditableMathLabel() @property (nonatomic) MTMathUILabel* label; @property (nonatomic) MTTapGestureRecognizer* tapGestureRecognizer; @@ -834,33 +834,6 @@ - (BOOL)hasText return NO; } -#pragma mark - UITextInputTraits - -- (UITextAutocapitalizationType)autocapitalizationType -{ - return UITextAutocapitalizationTypeNone; -} - -- (UITextAutocorrectionType)autocorrectionType -{ - return UITextAutocorrectionTypeNo; -} - -- (UIReturnKeyType)returnKeyType -{ - return UIReturnKeyDefault; -} - -- (UITextSpellCheckingType)spellCheckingType -{ - return UITextSpellCheckingTypeNo; -} - -- (UIKeyboardType)keyboardType -{ - return UIKeyboardTypeASCIICapable; -} - #pragma mark - Hit Testing @@ -917,120 +890,6 @@ - (void) clearHighlights [self.label setNeedsLayout]; } -#pragma mark - UITextInput - -// These are blank just to get a UITextInput implementation, to fix the dictation button bug. -// Proposed fix from: http://stackoverflow.com/questions/20980898/work-around-for-dictation-custom-text-view-bug - -@synthesize beginningOfDocument; -@synthesize endOfDocument; -@synthesize inputDelegate; -@synthesize markedTextRange; -@synthesize markedTextStyle; -@synthesize selectedTextRange; -@synthesize tokenizer; - -- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction -{ - return UITextWritingDirectionLeftToRight; -} - -- (CGRect)caretRectForPosition:(UITextPosition *)position -{ - return CGRectZero; -} - -- (void)unmarkText -{ - -} - -- (UITextRange *)characterRangeAtPoint:(CGPoint)point -{ - return nil; -} -- (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction -{ - return nil; -} -- (UITextPosition *)closestPositionToPoint:(CGPoint)point -{ - return nil; -} -- (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range -{ - return nil; -} -- (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other -{ - return NSOrderedSame; -} -- (void)dictationRecognitionFailed -{ -} -- (void)dictationRecordingDidEnd -{ -} -- (CGRect)firstRectForRange:(UITextRange *)range -{ - return CGRectZero; -} - -- (CGRect)frameForDictationResultPlaceholder:(id)placeholder -{ - return CGRectZero; -} -- (void)insertDictationResult:(NSArray *)dictationResult -{ -} -- (id)insertDictationResultPlaceholder -{ - return nil; -} - -- (NSInteger)offsetFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition -{ - return 0; -} -- (UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset -{ - return nil; -} -- (UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset -{ - return nil; -} - -- (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction -{ - return nil; -} -- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult -{ -} -- (void)replaceRange:(UITextRange *)range withText:(NSString *)text -{ -} -- (NSArray *)selectionRectsForRange:(UITextRange *)range -{ - return nil; -} -- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range -{ -} -- (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange -{ -} - -- (NSString *)textInRange:(UITextRange *)range -{ - return nil; -} -- (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition -{ - return nil; -} - @end #endif diff --git a/mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m b/mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m deleted file mode 100644 index 59128c6..0000000 --- a/mathEditor/editor/NSObject+MTEditableMathLabel_UITextInput.m +++ /dev/null @@ -1,12 +0,0 @@ -// -// NSObject+MTEditableMathLabel_UITextInput.m -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import "NSObject+MTEditableMathLabel_UITextInput.h" - -@implementation NSObject (MTEditableMathLabel_UITextInput) - -@end From 57c6924a110fe3217aeb1ac0b2af9540e3f973e6 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Fri, 20 Mar 2026 12:12:25 +0000 Subject: [PATCH 022/133] Fix --- mathEditor/editor/MTEditableMathLabel+UITextInput.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mathEditor/editor/MTEditableMathLabel+UITextInput.m b/mathEditor/editor/MTEditableMathLabel+UITextInput.m index 35c58d8..794fe80 100644 --- a/mathEditor/editor/MTEditableMathLabel+UITextInput.m +++ b/mathEditor/editor/MTEditableMathLabel+UITextInput.m @@ -36,6 +36,13 @@ - (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange { objc_setAssociatedObject(self, @selector(selectedTextRange), selectedTextRange, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (id)inputDelegate { + return objc_getAssociatedObject(self, @selector(inputDelegate)); +} +- (void)setInputDelegate:(id)inputDelegate { + objc_setAssociatedObject(self, @selector(inputDelegate), inputDelegate, OBJC_ASSOCIATION_ASSIGN); +} + - (nullable UITextRange *)markedTextRange { return objc_getAssociatedObject(self, @selector(markedTextRange)); } From 9a2f5415ed7c4971b2ef7e89b771a85302b3aeb0 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 12:26:28 +0000 Subject: [PATCH 023/133] handle weak ref correctly --- mathEditor/editor/MTEditableMathLabel+UITextInput.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel+UITextInput.m b/mathEditor/editor/MTEditableMathLabel+UITextInput.m index 794fe80..5307603 100644 --- a/mathEditor/editor/MTEditableMathLabel+UITextInput.m +++ b/mathEditor/editor/MTEditableMathLabel+UITextInput.m @@ -36,11 +36,15 @@ - (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange { objc_setAssociatedObject(self, @selector(selectedTextRange), selectedTextRange, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -- (id)inputDelegate { - return objc_getAssociatedObject(self, @selector(inputDelegate)); +- (nullable id)inputDelegate { + id (^block)(void) = objc_getAssociatedObject(self, @selector(inputDelegate)); + return block ? block() : nil; } -- (void)setInputDelegate:(id)inputDelegate { - objc_setAssociatedObject(self, @selector(inputDelegate), inputDelegate, OBJC_ASSOCIATION_ASSIGN); + +- (void)setInputDelegate:(nullable id)inputDelegate { + __weak id weakDelegate = inputDelegate; + id (^block)(void) = ^{ return weakDelegate; }; + objc_setAssociatedObject(self, @selector(inputDelegate), block, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (nullable UITextRange *)markedTextRange { From 6f84bca53c73a5c37a7c947a8380aa0796eef632 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 12:27:07 +0000 Subject: [PATCH 024/133] fmt --- .../editor/MTEditableMathLabel+UITextInput.m | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel+UITextInput.m b/mathEditor/editor/MTEditableMathLabel+UITextInput.m index 5307603..3b73909 100644 --- a/mathEditor/editor/MTEditableMathLabel+UITextInput.m +++ b/mathEditor/editor/MTEditableMathLabel+UITextInput.m @@ -7,9 +7,9 @@ #if TARGET_OS_IPHONE -#import #import -#import "MTEditableMathLabel.h" +#import +#import "MTEditableMathLabel.h" NS_ASSUME_NONNULL_BEGIN @@ -29,44 +29,56 @@ @implementation MTEditableMathLabel (UITextInput) //@synthesize selectedTextRange; //@synthesize tokenizer; -- (nullable UITextRange *)selectedTextRange { +- (nullable UITextRange *)selectedTextRange +{ return objc_getAssociatedObject(self, @selector(selectedTextRange)); } -- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange { +- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange +{ objc_setAssociatedObject(self, @selector(selectedTextRange), selectedTextRange, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -- (nullable id)inputDelegate { +- (nullable id)inputDelegate +{ id (^block)(void) = objc_getAssociatedObject(self, @selector(inputDelegate)); return block ? block() : nil; } -- (void)setInputDelegate:(nullable id)inputDelegate { +- (void)setInputDelegate:(nullable id)inputDelegate +{ __weak id weakDelegate = inputDelegate; - id (^block)(void) = ^{ return weakDelegate; }; + id (^block)(void) = ^{ + return weakDelegate; + }; objc_setAssociatedObject(self, @selector(inputDelegate), block, OBJC_ASSOCIATION_COPY_NONATOMIC); } -- (nullable UITextRange *)markedTextRange { +- (nullable UITextRange *)markedTextRange +{ return objc_getAssociatedObject(self, @selector(markedTextRange)); } -- (nullable NSDictionary *)markedTextStyle { +- (nullable NSDictionary *)markedTextStyle +{ return objc_getAssociatedObject(self, @selector(markedTextStyle)); } -- (void)setMarkedTextStyle:(nullable NSDictionary *)markedTextStyle { +- (void)setMarkedTextStyle:(nullable NSDictionary *)markedTextStyle +{ objc_setAssociatedObject(self, @selector(markedTextStyle), markedTextStyle, OBJC_ASSOCIATION_COPY_NONATOMIC); } -- (UITextPosition *)beginningOfDocument { +- (UITextPosition *)beginningOfDocument +{ return objc_getAssociatedObject(self, @selector(beginningOfDocument)); } -- (UITextPosition *)endOfDocument { +- (UITextPosition *)endOfDocument +{ return objc_getAssociatedObject(self, @selector(endOfDocument)); } -- (id)tokenizer { +- (id)tokenizer +{ id tokenizer = objc_getAssociatedObject(self, @selector(tokenizer)); if (!tokenizer) { tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self]; @@ -75,7 +87,8 @@ - (UITextPosition *)endOfDocument { return tokenizer; } -- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction +- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position + inDirection:(UITextStorageDirection)direction { return UITextWritingDirectionLeftToRight; } @@ -87,14 +100,14 @@ - (CGRect)caretRectForPosition:(UITextPosition *)position - (void)unmarkText { - } - (nullable UITextRange *)characterRangeAtPoint:(CGPoint)point { return nil; } -- (nullable UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction +- (nullable UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position + inDirection:(UITextLayoutDirection)direction { return nil; } @@ -137,7 +150,9 @@ - (NSInteger)offsetFromPosition:(UITextPosition *)fromPosition toPosition:(UITex { return 0; } -- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset +- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position + inDirection:(UITextLayoutDirection)direction + offset:(NSInteger)offset { return nil; } @@ -146,7 +161,8 @@ - (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position off return nil; } -- (nullable UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction +- (nullable UITextPosition *)positionWithinRange:(UITextRange *)range + farthestInDirection:(UITextLayoutDirection)direction { return nil; } From 6a8fbc905264a0bc3f1906d3691b338aacf624cd Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 13:19:19 +0000 Subject: [PATCH 025/133] Isolate UIResponder --- .../editor/MTEditableMathLabel+UIResponder.m | 66 +++++++++++++++++++ .../editor/MTEditableMathLabel+UITextInput.m | 2 +- mathEditor/editor/MTEditableMathLabel.h | 3 + mathEditor/editor/MTEditableMathLabel.m | 51 ++++---------- 4 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 mathEditor/editor/MTEditableMathLabel+UIResponder.m diff --git a/mathEditor/editor/MTEditableMathLabel+UIResponder.m b/mathEditor/editor/MTEditableMathLabel+UIResponder.m new file mode 100644 index 0000000..71a80c7 --- /dev/null +++ b/mathEditor/editor/MTEditableMathLabel+UIResponder.m @@ -0,0 +1,66 @@ +// +// MTEditableMathLabel+UIResponder.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#if TARGET_OS_IPHONE + +#import +#import +#import "MTEditableMathLabel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MTEditableMathLabel (UIResponder) + +@end + +@implementation MTEditableMathLabel (UIResponder) + +- (nullable UIView *)inputView +{ + return self.keyboard; +} + +/** + UIResponder protocol override. + Our view can become first responder to receive user text input. + */ +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (BOOL)becomeFirstResponder +{ + BOOL canBecome = [super becomeFirstResponder]; + if (canBecome) { + [self doBecomeFirstResponder]; + } else { + // Sometimes it takes some time + // [self performSelector:@selector(startEditing) withObject:nil afterDelay:0.0]; + } + return canBecome; +} + +/** + UIResponder protocol override. + Called when our view is being asked to resign first responder state. + */ +- (BOOL)resignFirstResponder +{ + BOOL val = YES; + if ([self isFirstResponder]) { + val = [super resignFirstResponder]; + [self doResignFirstResponder]; + } + return val; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/mathEditor/editor/MTEditableMathLabel+UITextInput.m b/mathEditor/editor/MTEditableMathLabel+UITextInput.m index 3b73909..e9f2dfc 100644 --- a/mathEditor/editor/MTEditableMathLabel+UITextInput.m +++ b/mathEditor/editor/MTEditableMathLabel+UITextInput.m @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface MTEditableMathLabel () +@interface MTEditableMathLabel (UITextInput) @end @implementation MTEditableMathLabel (UITextInput) diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index 57a0703..961337a 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -90,4 +90,7 @@ - (CGSize) mathDisplaySize; +// Compatibility? +- (void)doBecomeFirstResponder; +- (void)doResignFirstResponder; @end diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 72bb7e0..5a43e86 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -173,57 +173,32 @@ - (CGSize) mathDisplaySize #pragma mark - Custom user interaction -- (UIView *)inputView -{ - return self.keyboard; -} -/** - UIResponder protocol override. - Our view can become first responder to receive user text input. - */ -- (BOOL)canBecomeFirstResponder +- (BOOL)doBecomeFirstResponder { - return YES; -} + if (_insertionIndex == nil) { + _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count]; + } -- (BOOL)becomeFirstResponder -{ - BOOL canBecome = [super becomeFirstResponder]; - if (canBecome) { - if (_insertionIndex == nil) { - _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count]; - } + [self.keyboard startedEditing:self]; - [self.keyboard startedEditing:self]; - - [self insertionPointChanged]; - if ([self.delegate respondsToSelector:@selector(didBeginEditing:)]) { - [self.delegate didBeginEditing:self]; - } - } else { - // Sometimes it takes some time - // [self performSelector:@selector(startEditing) withObject:nil afterDelay:0.0]; + [self insertionPointChanged]; + if ([self.delegate respondsToSelector:@selector(didBeginEditing:)]) { + [self.delegate didBeginEditing:self]; } - return canBecome; } /** UIResponder protocol override. Called when our view is being asked to resign first responder state. */ -- (BOOL)resignFirstResponder +- (BOOL)doResignFirstResponder { - BOOL val = YES; - if ([self isFirstResponder]) { - [self.keyboard finishedEditing:self]; - val = [super resignFirstResponder]; - [self insertionPointChanged]; - if ([self.delegate respondsToSelector:@selector(didEndEditing:)]) { - [self.delegate didEndEditing:self]; - } + [self.keyboard finishedEditing:self]; + [self insertionPointChanged]; + if ([self.delegate respondsToSelector:@selector(didEndEditing:)]) { + [self.delegate didEndEditing:self]; } - return val; } - (void) startEditing From 8740da228fa5116a0962c75dd5329e7e385587c9 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 15:22:22 +0000 Subject: [PATCH 026/133] Handle Responder for macos --- ...nder.m => MTEditableMathLabel+Responder.m} | 11 +++++--- mathEditor/internal/MTView+FirstResponder.h | 22 ++++++++++++++++ mathEditor/internal/MTView+FirstResponder.m | 26 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) rename mathEditor/editor/{MTEditableMathLabel+UIResponder.m => MTEditableMathLabel+Responder.m} (83%) create mode 100644 mathEditor/internal/MTView+FirstResponder.h create mode 100644 mathEditor/internal/MTView+FirstResponder.m diff --git a/mathEditor/editor/MTEditableMathLabel+UIResponder.m b/mathEditor/editor/MTEditableMathLabel+Responder.m similarity index 83% rename from mathEditor/editor/MTEditableMathLabel+UIResponder.m rename to mathEditor/editor/MTEditableMathLabel+Responder.m index 71a80c7..00b787f 100644 --- a/mathEditor/editor/MTEditableMathLabel+UIResponder.m +++ b/mathEditor/editor/MTEditableMathLabel+Responder.m @@ -8,21 +8,24 @@ #if TARGET_OS_IPHONE #import -#import +#import "MTConfig.h" #import "MTEditableMathLabel.h" +#import "MTView+FirstResponder.h" +// #import "AppKit/AppKit.h" NS_ASSUME_NONNULL_BEGIN -@interface MTEditableMathLabel (UIResponder) - +@interface MTEditableMathLabel (Responder) @end -@implementation MTEditableMathLabel (UIResponder) +@implementation MTEditableMathLabel (Responder) +#if TARGET_OS_IPHONE - (nullable UIView *)inputView { return self.keyboard; } +#endif // TARGET_OS_IPHONE /** UIResponder protocol override. diff --git a/mathEditor/internal/MTView+FirstResponder.h b/mathEditor/internal/MTView+FirstResponder.h new file mode 100644 index 0000000..67a2b94 --- /dev/null +++ b/mathEditor/internal/MTView+FirstResponder.h @@ -0,0 +1,22 @@ +// +// MTView+FirstResponder.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +#if TARGET_OS_OSX + +@interface NSView (FirstResponder) + +@property (nonatomic, readonly) BOOL isFirstResponder; + +@end + +#endif // TARGET_OS_OSX + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView+FirstResponder.m b/mathEditor/internal/MTView+FirstResponder.m new file mode 100644 index 0000000..df4cda9 --- /dev/null +++ b/mathEditor/internal/MTView+FirstResponder.m @@ -0,0 +1,26 @@ +// +// MTView+FirstResponder.m +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTConfig.h" +#import "MTView+FirstResponder.h" + +NS_ASSUME_NONNULL_BEGIN + +#if TARGET_OS_OSX + +@implementation NSView (FirstResponder) + +- (BOOL)isFirstResponder +{ + return self.window.firstResponder == self; +} + +@end + +#endif + +NS_ASSUME_NONNULL_END From 91ee29c49a738f69206dd9a8b8d735f840fe89f8 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 15:28:19 +0000 Subject: [PATCH 027/133] For now, make MTCareView no-op on macos but comileable --- mathEditor/internal/MTCaretView.m | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index ba16c8c..f3fc3ac 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -8,8 +8,6 @@ // MIT license. See the LICENSE file for details. // -#if TARGET_OS_IPHONE - #import "MTCaretView.h" #import "MTEditableMathLabel.h" #import "MTConfig.h" @@ -36,6 +34,8 @@ @interface MTCaretHandle : MTView @end +#if TARGET_OS_IPHONE + @implementation MTCaretHandle { UIBezierPath* _path; MTColor* _color; @@ -114,6 +114,8 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event @end +#endif + @interface MTCaretView () @property (nonatomic) NSTimer *blinkTimer; @@ -122,7 +124,7 @@ @interface MTCaretView () @implementation MTCaretView { - UIView *_blinker; + MTView *_blinker; MTCaretHandle *_handle; CGFloat _scale; } @@ -131,6 +133,7 @@ - (id)initWithEditor:(MTEditableMathLabel*)label { self = [super initWithFrame:CGRectZero]; if (self) { +#if TARGET_OS_IPHONE _scale = label.fontSize / kCaretFontSize; _blinker = [[UIView alloc] initWithFrame:CGRectZero]; _blinker.backgroundColor = self.caretColor; @@ -140,6 +143,7 @@ - (id)initWithEditor:(MTEditableMathLabel*)label _handle.hidden = YES; _handle.label = label; [self addSubview:_handle]; +#endif } return self; } @@ -154,7 +158,9 @@ - (void)setPosition:(CGPoint)position - (void) setFontSize:(CGFloat)fontSize { _scale = fontSize / kCaretFontSize; +#if TARGET_OS_IPHONE [self setNeedsLayout]; +#endif } - (void) layoutSubviews @@ -208,10 +214,13 @@ - (void)dealloc - (void)setCaretColor:(MTColor *)caretColor { _caretColor = caretColor; +#if TARGET_OS_IPHONE _handle.color = caretColor; +#endif _blinker.backgroundColor = self.caretColor; } +#if TARGET_OS_IPHONE - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{ if (!_handle.hidden) { return [_handle pointInside:[self convertPoint:point toView:_handle] withEvent:event]; @@ -219,8 +228,6 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{ return [super pointInside:point withEvent:event]; } } - +#endif @end - -#endif From f7e4de3f05ae5094a1a0c05fe3fd952c6e59fb02 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 16:21:37 +0000 Subject: [PATCH 028/133] New organization for categories --- .../editor/MTEditableMathLabel+Responder.m | 2 +- mathEditor/internal/MTCancelView.m | 11 ++------ .../internal/MTView/MTView+AutoLayout.h | 18 +++++++++++++ .../internal/MTView/MTView+AutoLayout.m | 25 +++++++++++++++++++ .../{ => MTView}/MTView+FirstResponder.h | 0 .../{ => MTView}/MTView+FirstResponder.m | 0 mathEditor/internal/MTView/MXView.h | 14 +++++++++++ 7 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 mathEditor/internal/MTView/MTView+AutoLayout.h create mode 100644 mathEditor/internal/MTView/MTView+AutoLayout.m rename mathEditor/internal/{ => MTView}/MTView+FirstResponder.h (100%) rename mathEditor/internal/{ => MTView}/MTView+FirstResponder.m (100%) create mode 100644 mathEditor/internal/MTView/MXView.h diff --git a/mathEditor/editor/MTEditableMathLabel+Responder.m b/mathEditor/editor/MTEditableMathLabel+Responder.m index 00b787f..aa543ec 100644 --- a/mathEditor/editor/MTEditableMathLabel+Responder.m +++ b/mathEditor/editor/MTEditableMathLabel+Responder.m @@ -10,7 +10,7 @@ #import #import "MTConfig.h" #import "MTEditableMathLabel.h" -#import "MTView+FirstResponder.h" +#import "MTView/MTView+FirstResponder.h" // #import "AppKit/AppKit.h" NS_ASSUME_NONNULL_BEGIN diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m index 70ce8d3..52c445f 100644 --- a/mathEditor/internal/MTCancelView.m +++ b/mathEditor/internal/MTCancelView.m @@ -6,6 +6,7 @@ #import "MTCancelView.h" #import "MTTapGestureRecognizer.h" +#import "MTView/MTView+AutoLayout.h" @interface MTCancelView () @@ -29,25 +30,17 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } _imageView = [[UIImageView alloc] initWithImage:image]; - _imageView.translatesAutoresizingMaskIntoConstraints = NO; _imageView.contentMode = UIViewContentModeScaleAspectFit; _imageView.tintColor = [MTColor secondaryLabelColor]; #else NSImage *image = [NSImage imageWithSystemSymbolName:@"xmark.circle" accessibilityDescription:nil]; _imageView = [[NSImageView alloc] initWithFrame:CGRectZero]; _imageView.image = image; - _imageView.translatesAutoresizingMaskIntoConstraints = NO; _imageView.imageScaling = NSImageScaleProportionallyUpOrDown; _imageView.contentTintColor = [MTColor secondaryLabelColor]; #endif [self addSubview:_imageView]; - - [NSLayoutConstraint activateConstraints:@[ - [_imageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], - [_imageView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], - [_imageView.topAnchor constraintEqualToAnchor:self.topAnchor], - [_imageView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], - ]]; + [_imageView pinToSuperview]; #if TARGET_OS_IPHONE self.userInteractionEnabled = YES; diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.h b/mathEditor/internal/MTView/MTView+AutoLayout.h new file mode 100644 index 0000000..dddb124 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+AutoLayout.h @@ -0,0 +1,18 @@ +// +// MTView+AutoLayout.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MXView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXView (AutoLayout) + +- (void)pinToSuperview; + +@end + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.m b/mathEditor/internal/MTView/MTView+AutoLayout.m new file mode 100644 index 0000000..85c96c9 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+AutoLayout.m @@ -0,0 +1,25 @@ +// +// MTView+AutoLayout.m +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTConfig.h" +#import "MTView+AutoLayout.h" + +@implementation MXView (AutoLayout) + +- (void)pinToSuperview +{ + MTView *superview = self.superview; + self.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [self.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor], + [self.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor], + [self.topAnchor constraintEqualToAnchor:superview.topAnchor], + [self.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor] + ]]; +} + +@end diff --git a/mathEditor/internal/MTView+FirstResponder.h b/mathEditor/internal/MTView/MTView+FirstResponder.h similarity index 100% rename from mathEditor/internal/MTView+FirstResponder.h rename to mathEditor/internal/MTView/MTView+FirstResponder.h diff --git a/mathEditor/internal/MTView+FirstResponder.m b/mathEditor/internal/MTView/MTView+FirstResponder.m similarity index 100% rename from mathEditor/internal/MTView+FirstResponder.m rename to mathEditor/internal/MTView/MTView+FirstResponder.m diff --git a/mathEditor/internal/MTView/MXView.h b/mathEditor/internal/MTView/MXView.h new file mode 100644 index 0000000..d781e72 --- /dev/null +++ b/mathEditor/internal/MTView/MXView.h @@ -0,0 +1,14 @@ +// +// MXView.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#if TARGET_OS_OSX +#import +#define MXView NSView +#else +#import +#define MXView UIView +#endif From 2b322d926a14d166aee6a9f617f4fa683fc3617d Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 16:29:42 +0000 Subject: [PATCH 029/133] Use pinToSuperview --- mathEditor/editor/MTEditableMathLabel.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 5a43e86..4e72ea3 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -21,6 +21,7 @@ #import "MTTapGestureRecognizer.h" #import "MTMathList+Editing.h" #import "MTDisplay+Editing.h" +#import "MTView/MTView+AutoLayout.h" #import "MTUnicode.h" #import "MTMathListBuilder.h" @@ -76,12 +77,13 @@ - (void) initialize self.mathList = [MTMathList new]; self.userInteractionEnabled = YES; + // TODO: do we need this? self.autoresizesSubviews = YES; // Create and set up the APLSimpleCoreTextView that will do the drawing. MTMathUILabel *label = [[MTMathUILabel alloc] initWithFrame:self.bounds]; - label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self addSubview:label]; + [label pinToSuperview]; label.fontSize = 30; label.backgroundColor = self.backgroundColor; label.userInteractionEnabled = NO; From 09aecce366012021f7a73970f1e6f8cd0d9e4a67 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 17:00:12 +0000 Subject: [PATCH 030/133] A few Layout compatibility methods --- mathEditor/editor/MTEditableMathLabel.m | 2 ++ mathEditor/internal/MTView/MTView+Layout.h | 26 ++++++++++++++++++ mathEditor/internal/MTView/MTView+Layout.m | 31 ++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 mathEditor/internal/MTView/MTView+Layout.h create mode 100644 mathEditor/internal/MTView/MTView+Layout.m diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 4e72ea3..cff5d60 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -22,6 +22,8 @@ #import "MTMathList+Editing.h" #import "MTDisplay+Editing.h" #import "MTView/MTView+AutoLayout.h" +#import "MTView/MTView+Layout.h" +#import "MTView/MTView+FirstResponder.h" #import "MTUnicode.h" #import "MTMathListBuilder.h" diff --git a/mathEditor/internal/MTView/MTView+Layout.h b/mathEditor/internal/MTView/MTView+Layout.h new file mode 100644 index 0000000..76248e7 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+Layout.h @@ -0,0 +1,26 @@ +// +// MTView+Layout.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MXView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXView (Layout) + +#if TARGET_OS_OSX + +- (void)setNeedsLayout; + +- (void)setNeedsDisplay; + +- (void)layoutIfNeeded; + +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+Layout.m b/mathEditor/internal/MTView/MTView+Layout.m new file mode 100644 index 0000000..8161b17 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+Layout.m @@ -0,0 +1,31 @@ +// +// MTView+Layout.m +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTView+Layout.h" + +@implementation MXView (Layout) + +#if TARGET_OS_OSX + +- (void)setNeedsLayout +{ + [self setNeedsLayout:YES]; +} + +- (void)setNeedsDisplay +{ + [self setNeedsDisplay:YES]; +} + +- (void)layoutIfNeeded +{ + [self layoutSubtreeIfNeeded]; +} + +#endif + +@end From 1deff5bf6c35257694739161661c11c561013335 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 17:50:53 +0000 Subject: [PATCH 031/133] Fist version without top-level ios guard --- mathEditor/editor/MTEditableMathLabel.m | 37 ++++++++++++++++++---- mathEditor/internal/MTView/MTView+Layout.h | 2 ++ mathEditor/internal/MTView/MTView+Layout.m | 8 +++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index cff5d60..82b79f8 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -8,7 +8,7 @@ // MIT license. See the LICENSE file for details. // -#if TARGET_OS_IPHONE +//#if TARGET_OS_IPHONE #import @@ -28,7 +28,12 @@ #import "MTUnicode.h" #import "MTMathListBuilder.h" -@interface MTEditableMathLabel() +// TODO: move this method declaration to iosMath. +@interface MTMathUILabel (SizeThatFits) +- (CGSize)sizeThatFits:(CGSize)size; +@end + +@interface MTEditableMathLabel() @property (nonatomic) MTMathUILabel* label; @property (nonatomic) MTTapGestureRecognizer* tapGestureRecognizer; @@ -77,8 +82,9 @@ - (void) initialize // Create our text storage. self.mathList = [MTMathList new]; - - self.userInteractionEnabled = YES; + + // This value is `YES` by default. + // self.userInteractionEnabled = YES; // TODO: do we need this? self.autoresizesSubviews = YES; @@ -88,7 +94,10 @@ - (void) initialize [label pinToSuperview]; label.fontSize = 30; label.backgroundColor = self.backgroundColor; + + #if TARGET_OS_IPHONE label.userInteractionEnabled = NO; + #endif label.textAlignment = kMTTextAlignmentCenter; self.label = label; [self createCancelImage]; @@ -106,10 +115,22 @@ - (void) initialize self.mathList = [MTMathList new]; } +#if TARGET_OS_IPHONE -(void)layoutSubviews { [super layoutSubviews]; - + [self doLayout]; +} +#endif + +#if TARGET_OS_OSX +- (void)layout { + [super layout]; + [self doLayout]; +} +#endif + +-(void)doLayout { CGRect frame = CGRectMake(self.frame.size.width - 55, (self.frame.size.height - 45)/2, 45, 45); self.cancelImage.frame = frame; @@ -837,6 +858,8 @@ - (CGPoint)caretRectForIndex:(MTMathListIndex *)index return [self.label.displayList caretPositionForIndex:index]; } +#if TARGET_OS_IPHONE + - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { BOOL inside = [super pointInside:point withEvent:event]; @@ -847,6 +870,8 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event return [_caretView pointInside:[self convertPoint:point toView:_caretView] withEvent:event]; } +#endif // TARGET_OS_IPHONE + #pragma mark - Highlighting - (void)highlightCharacterAtIndex:(MTMathListIndex *)index @@ -871,4 +896,4 @@ - (void) clearHighlights @end -#endif +//#endif diff --git a/mathEditor/internal/MTView/MTView+Layout.h b/mathEditor/internal/MTView/MTView+Layout.h index 76248e7..fdeb22b 100644 --- a/mathEditor/internal/MTView/MTView+Layout.h +++ b/mathEditor/internal/MTView/MTView+Layout.h @@ -19,6 +19,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)layoutIfNeeded; +- (void)bringSubviewToFront:(NSView *)view; + #endif @end diff --git a/mathEditor/internal/MTView/MTView+Layout.m b/mathEditor/internal/MTView/MTView+Layout.m index 8161b17..900f7b0 100644 --- a/mathEditor/internal/MTView/MTView+Layout.m +++ b/mathEditor/internal/MTView/MTView+Layout.m @@ -26,6 +26,14 @@ - (void)layoutIfNeeded [self layoutSubtreeIfNeeded]; } +- (void)bringSubviewToFront:(NSView *)view { + if (view.superview != self) { + return; + } + [view removeFromSuperview]; + [self addSubview:view]; +} + #endif @end From f02dd3926d49a84a49bbdaece3cd7b5950d87ddc Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 17:56:52 +0000 Subject: [PATCH 032/133] Update MacOS example --- .../ContentView.swift | 65 +++++++++++-------- .../MathEditorSwiftUIExampleApp.swift | 8 +-- Package.swift | 6 +- mathEditor/editor/MTEditableMathLabel.m | 4 -- mathEditor/internal/MTView/MTView+Layout.m | 5 +- 5 files changed, 47 insertions(+), 41 deletions(-) diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index 302b44b..c39e9c1 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -1,44 +1,53 @@ // Copyright © 2025 Snap, Inc. All rights reserved. -import SwiftUI import MathEditor - +import SwiftUI struct ContentView: View { - var body: some View { - MathEditorView() - .padding() - } + var body: some View { + MathEditorView() + .padding() + } } #Preview { - ContentView() + ContentView() } #if os(iOS) -import MathKeyboard - -struct MathEditorView : UIViewRepresentable { - typealias UIViewType = MTEditableMathLabel - - func makeUIView(context: Context) -> MTEditableMathLabel { - let mathLabel = MTEditableMathLabel() - mathLabel.backgroundColor = .clear - mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance(); - return mathLabel - } - - func updateUIView(_ uiView: MTEditableMathLabel, context: Context) { - + import MathKeyboard + + struct MathEditorView: UIViewRepresentable { + typealias UIViewType = MTEditableMathLabel + + func makeUIView(context: Context) -> MTEditableMathLabel { + let mathLabel = MTEditableMathLabel() + mathLabel.backgroundColor = .clear + mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance() + return mathLabel + } + + func updateUIView(_ uiView: MTEditableMathLabel, context: Context) { + + } } -} -#endif +#endif // os(iOS) #if os(macOS) -struct MathEditorView: View { - var body: some View { - Text("MathEditor is not wired up for macOS in this example yet.") - .frame(maxWidth: .infinity, maxHeight: .infinity) + + struct MathEditorView: NSViewRepresentable { + typealias UIViewType = MTEditableMathLabel + + func makeNSView(context: Context) -> MTEditableMathLabel { + let mathLabel = MTEditableMathLabel() + mathLabel.backgroundColor = .clear + // mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance(); + return mathLabel + } + + func updateNSView(_ uiView: MTEditableMathLabel, context: Context) { + + } } -} + #endif diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift index 7aa66b6..d70240a 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift @@ -4,9 +4,9 @@ import SwiftUI @main struct MathEditorSwiftUIExampleApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } + var body: some Scene { + WindowGroup { + ContentView() } + } } diff --git a/Package.swift b/Package.swift index bba9576..9f1716e 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["MathEditor"]), .library( name: "MathKeyboard", - targets: ["MathKeyboard"]) + targets: ["MathKeyboard"]), ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") @@ -33,14 +33,14 @@ let package = Package( path: "./mathKeyboard", resources: [.process("MathKeyboardResources")], cSettings: [ - .headerSearchPath("./keyboard"), + .headerSearchPath("./keyboard") ] ), .testTarget( name: "MathEditorTests", dependencies: ["MathEditor"], path: "Tests", - cSettings: [ + cSettings: [ .headerSearchPath("../mathEditor/editor"), .headerSearchPath("../mathEditor/keyboard"), .headerSearchPath("../mathEditor/internal"), diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 82b79f8..7a5f0ec 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -8,8 +8,6 @@ // MIT license. See the LICENSE file for details. // -//#if TARGET_OS_IPHONE - #import #import "MTEditableMathLabel.h" @@ -895,5 +893,3 @@ - (void) clearHighlights } @end - -//#endif diff --git a/mathEditor/internal/MTView/MTView+Layout.m b/mathEditor/internal/MTView/MTView+Layout.m index 900f7b0..0d43c50 100644 --- a/mathEditor/internal/MTView/MTView+Layout.m +++ b/mathEditor/internal/MTView/MTView+Layout.m @@ -26,9 +26,10 @@ - (void)layoutIfNeeded [self layoutSubtreeIfNeeded]; } -- (void)bringSubviewToFront:(NSView *)view { +- (void)bringSubviewToFront:(NSView *)view +{ if (view.superview != self) { - return; + return; } [view removeFromSuperview]; [self addSubview:view]; From fd0881d43b48977b272a5a96be96ee4ae93d2887 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 18:18:00 +0000 Subject: [PATCH 033/133] Introduce UIView and NSView categories --- .../editor/MTEditableMathLabel+NSView.m | 28 ++++++++++++++++++ .../editor/MTEditableMathLabel+Responder.m | 14 ++++++--- .../editor/MTEditableMathLabel+UIView.m | 29 +++++++++++++++++++ mathEditor/editor/MTEditableMathLabel.h | 1 + mathEditor/editor/MTEditableMathLabel.m | 15 ---------- 5 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 mathEditor/editor/MTEditableMathLabel+NSView.m create mode 100644 mathEditor/editor/MTEditableMathLabel+UIView.m diff --git a/mathEditor/editor/MTEditableMathLabel+NSView.m b/mathEditor/editor/MTEditableMathLabel+NSView.m new file mode 100644 index 0000000..9c87f8a --- /dev/null +++ b/mathEditor/editor/MTEditableMathLabel+NSView.m @@ -0,0 +1,28 @@ +// +// MTEditableMathLabel+NSView.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#if TARGET_OS_OSX + +#import "MTEditableMathLabel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MTEditableMathLabel (NSView) +@end + +@implementation MTEditableMathLabel(NSView) + +- (void)layout { + [super layout]; + [self doLayout]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/mathEditor/editor/MTEditableMathLabel+Responder.m b/mathEditor/editor/MTEditableMathLabel+Responder.m index aa543ec..a3de4f6 100644 --- a/mathEditor/editor/MTEditableMathLabel+Responder.m +++ b/mathEditor/editor/MTEditableMathLabel+Responder.m @@ -5,13 +5,10 @@ // Created by Madiyar Aitbayev on 20/03/2026. // -#if TARGET_OS_IPHONE - #import #import "MTConfig.h" #import "MTEditableMathLabel.h" #import "MTView/MTView+FirstResponder.h" -// #import "AppKit/AppKit.h" NS_ASSUME_NONNULL_BEGIN @@ -21,12 +18,22 @@ @interface MTEditableMathLabel (Responder) @implementation MTEditableMathLabel (Responder) #if TARGET_OS_IPHONE + - (nullable UIView *)inputView { return self.keyboard; } + #endif // TARGET_OS_IPHONE +/** + NSResponder protocol override. + Our view can become first responder to receive user text input. + */ +- (BOOL)acceptsFirstResponder { + return [self canBecomeFirstResponder]; +} + /** UIResponder protocol override. Our view can become first responder to receive user text input. @@ -66,4 +73,3 @@ - (BOOL)resignFirstResponder NS_ASSUME_NONNULL_END -#endif diff --git a/mathEditor/editor/MTEditableMathLabel+UIView.m b/mathEditor/editor/MTEditableMathLabel+UIView.m new file mode 100644 index 0000000..5bf1675 --- /dev/null +++ b/mathEditor/editor/MTEditableMathLabel+UIView.m @@ -0,0 +1,29 @@ +// +// MTEditableMathLabel+NSView.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#if TARGET_OS_IPHONE + +#import "MTEditableMathLabel.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MTEditableMathLabel (UIView) +@end + +@implementation MTEditableMathLabel(UIView) + +-(void)layoutSubviews +{ + [super layoutSubviews]; + [self doLayout]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IPHONE diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index 961337a..0ce4293 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -91,6 +91,7 @@ - (CGSize) mathDisplaySize; // Compatibility? +- (void)doLayout; - (void)doBecomeFirstResponder; - (void)doResignFirstResponder; @end diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 7a5f0ec..05d69a8 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -113,21 +113,6 @@ - (void) initialize self.mathList = [MTMathList new]; } -#if TARGET_OS_IPHONE --(void)layoutSubviews -{ - [super layoutSubviews]; - [self doLayout]; -} -#endif - -#if TARGET_OS_OSX -- (void)layout { - [super layout]; - [self doLayout]; -} -#endif - -(void)doLayout { CGRect frame = CGRectMake(self.frame.size.width - 55, (self.frame.size.height - 45)/2, 45, 45); self.cancelImage.frame = frame; From b1fa7b905e9cbb1c132e3c43fac77028e40ef975 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 19:10:39 +0000 Subject: [PATCH 034/133] CaretView works on MacOS mostly --- mathEditor/editor/MTEditableMathLabel.m | 5 +++-- mathEditor/internal/MTCaretView.m | 29 +++++++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 05d69a8..3573b00 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -237,7 +237,7 @@ - (void)handleTapAtPoint:(CGPoint)tapPoint if (_insertionIndex == nil) { _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count]; } - [_caretView showHandle:NO]; + [_caretView showHandle:YES]; [self insertionPointChanged]; } } @@ -246,12 +246,13 @@ - (void)clear { self.mathList = [MTMathList new]; [self insertionPointChanged]; + [_caretView showHandle:NO]; } - (void)moveCaretToPoint:(CGPoint)point { _insertionIndex = [self closestIndexToPoint:point]; - [_caretView showHandle:NO]; +// [_caretView showHandle:NO]; [self insertionPointChanged]; } diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index f3fc3ac..0eec869 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -11,6 +11,7 @@ #import "MTCaretView.h" #import "MTEditableMathLabel.h" #import "MTConfig.h" +#import "MTView/MTView+Layout.h" static const NSTimeInterval InitialBlinkDelay = 0.7; static const NSTimeInterval BlinkRate = 0.5; @@ -133,11 +134,11 @@ - (id)initWithEditor:(MTEditableMathLabel*)label { self = [super initWithFrame:CGRectZero]; if (self) { -#if TARGET_OS_IPHONE _scale = label.fontSize / kCaretFontSize; - _blinker = [[UIView alloc] initWithFrame:CGRectZero]; + _blinker = [[MTView alloc] initWithFrame:CGRectZero]; _blinker.backgroundColor = self.caretColor; [self addSubview:_blinker]; +#if TARGET_OS_IPHONE _handle = [[MTCaretHandle alloc] initWithFrame:CGRectMake(0, 0, kCaretHandleWidth * _scale, kCaretHandleHeight *_scale)]; _handle.backgroundColor = [MTColor clearColor]; _handle.hidden = YES; @@ -158,12 +159,25 @@ - (void)setPosition:(CGPoint)position - (void) setFontSize:(CGFloat)fontSize { _scale = fontSize / kCaretFontSize; -#if TARGET_OS_IPHONE [self setNeedsLayout]; -#endif } +#if TARGET_OS_IPHONE - (void) layoutSubviews +{ + [super layoutSubviews]; + [self doLayout]; +} +#endif // TARGET_OS_IPHONE + +#if TARGET_OS_OSX +- (void) layout { + [super layout]; + [self doLayout]; +} +#endif // TARGET_OS_IPHONE + +- (void) doLayout { _blinker.frame = CGRectMake(0, 0, kCaretWidth * _scale, getCaretHeight() *_scale); _handle.frame = CGRectMake(-(kCaretHandleWidth - kCaretWidth) * _scale/2, (getCaretHeight() + kCaretHandleDescent) *_scale, kCaretHandleWidth *_scale, kCaretHandleHeight *_scale); @@ -196,6 +210,13 @@ - (void)didMoveToSuperview } } +#if TARGET_OS_OSX +- (void)viewDidMoveToSuperview +{ + [super viewDidMoveToSuperview]; + [self didMoveToSuperview]; +} +#endif // Helper method to set an initial blink delay - (void)delayBlink From 2679f850b2f2d7402d5d65fef665a872e972c3d7 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 20:18:57 +0000 Subject: [PATCH 035/133] Handle coordinate system correctly --- mathEditor/editor/MTEditableMathLabel+NSView.m | 4 ++++ mathEditor/editor/MTEditableMathLabel+Responder.m | 15 +++++++++++++++ mathEditor/internal/MTCaretView.m | 9 +++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel+NSView.m b/mathEditor/editor/MTEditableMathLabel+NSView.m index 9c87f8a..8b93eaa 100644 --- a/mathEditor/editor/MTEditableMathLabel+NSView.m +++ b/mathEditor/editor/MTEditableMathLabel+NSView.m @@ -21,6 +21,10 @@ - (void)layout { [self doLayout]; } +- (BOOL)isFlipped { + return YES; +} + @end NS_ASSUME_NONNULL_END diff --git a/mathEditor/editor/MTEditableMathLabel+Responder.m b/mathEditor/editor/MTEditableMathLabel+Responder.m index a3de4f6..0e3e4d7 100644 --- a/mathEditor/editor/MTEditableMathLabel+Responder.m +++ b/mathEditor/editor/MTEditableMathLabel+Responder.m @@ -69,6 +69,21 @@ - (BOOL)resignFirstResponder return val; } + +#if TARGET_OS_OSX + +- (void)keyDown:(NSEvent *)event { + // interpretKeyEvents feeds the event into the input system, + // which calls insertText: or deleteBackward: as appropriate. + [self interpretKeyEvents:@[event]]; +} + +- (void)deleteBackward:(nullable id)sender { + [self deleteBackward]; +} + +#endif // TARGET_OS_OSX + @end NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index 0eec869..dbe5e6e 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -171,11 +171,17 @@ - (void) layoutSubviews #endif // TARGET_OS_IPHONE #if TARGET_OS_OSX + - (void) layout { [super layout]; [self doLayout]; } -#endif // TARGET_OS_IPHONE + +- (BOOL)isFlipped { + return YES; +} + +#endif // TARGET_OS_OSX - (void) doLayout { @@ -194,7 +200,6 @@ - (void)blink _blinker.hidden = !_blinker.hidden; } - // UIView didMoveToSuperview override to set up blink timers after caret view created in superview. - (void)didMoveToSuperview { From 5a8820cacfea4f2eae34f690862e0ec39e2775a6 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 20:22:35 +0000 Subject: [PATCH 036/133] re-org --- mathEditor/internal/MTCaretView.m | 54 ++++++++++++++----------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index dbe5e6e..a109b41 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -162,27 +162,6 @@ - (void) setFontSize:(CGFloat)fontSize [self setNeedsLayout]; } -#if TARGET_OS_IPHONE -- (void) layoutSubviews -{ - [super layoutSubviews]; - [self doLayout]; -} -#endif // TARGET_OS_IPHONE - -#if TARGET_OS_OSX - -- (void) layout { - [super layout]; - [self doLayout]; -} - -- (BOOL)isFlipped { - return YES; -} - -#endif // TARGET_OS_OSX - - (void) doLayout { _blinker.frame = CGRectMake(0, 0, kCaretWidth * _scale, getCaretHeight() *_scale); @@ -215,14 +194,6 @@ - (void)didMoveToSuperview } } -#if TARGET_OS_OSX -- (void)viewDidMoveToSuperview -{ - [super viewDidMoveToSuperview]; - [self didMoveToSuperview]; -} -#endif - // Helper method to set an initial blink delay - (void)delayBlink { @@ -254,6 +225,29 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{ return [super pointInside:point withEvent:event]; } } -#endif + +- (void) layoutSubviews +{ + [super layoutSubviews]; + [self doLayout]; +} +#endif // TARGET_OS_IPHONE + +#if TARGET_OS_OSX +- (void)viewDidMoveToSuperview +{ + [super viewDidMoveToSuperview]; + [self didMoveToSuperview]; +} + +- (void) layout { + [super layout]; + [self doLayout]; +} + +- (BOOL)isFlipped { + return YES; +} +#endif // TARGET_OS_OSX @end From 207dec073c763e3c8e79da14757cbb5a827e1380 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 21:10:10 +0000 Subject: [PATCH 037/133] Remove unused code --- .clang-format | 15 +++++++++++ .../ContentView.swift | 2 ++ format.sh | 27 +++++++++++++++++++ mathEditor/editor/MTEditableMathLabel.m | 5 ---- 4 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 .clang-format create mode 100755 format.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b9661ce --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +--- +BasedOnStyle: Chromium +AlignTrailingComments: true +BreakBeforeBraces: Linux +ColumnLimit: 120 +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 2 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PointerBindsToType: false +SpacesBeforeTrailingComments: 1 +TabWidth: 8 +UseTab: Never +... diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index c39e9c1..a528faf 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -6,6 +6,8 @@ import SwiftUI struct ContentView: View { var body: some View { MathEditorView() + .background(Color.gray.opacity(0.1)) + .frame(maxHeight: 100) .padding() } } diff --git a/format.sh b/format.sh new file mode 100755 index 0000000..ede1b3c --- /dev/null +++ b/format.sh @@ -0,0 +1,27 @@ +set -euo pipefail + +clang_format() { + local dir=$1 + echo "Formatting $dir\n\n" + for file in $(find $dir -name '*.h' -or -name '*.m' -or -name '*.mm'); do + echo "Formatting $file" + clang-format $file -i + done +} + +format() { + local dir=$1 + echo "Formatting $dir" + swift format --in-place --parallel -r $dir +} + +lint() { + local dir=$1 + echo "Linting $dir" + swift format lint --strict --parallel -r $dir +} + +format . +lint . + +clang_format ./mathEditor/internal/MTView diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 3573b00..b46c026 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -81,11 +81,6 @@ - (void) initialize self.mathList = [MTMathList new]; - // This value is `YES` by default. - // self.userInteractionEnabled = YES; - // TODO: do we need this? - self.autoresizesSubviews = YES; - // Create and set up the APLSimpleCoreTextView that will do the drawing. MTMathUILabel *label = [[MTMathUILabel alloc] initWithFrame:self.bounds]; [self addSubview:label]; From 25192b7e32a8d0da0e150011a2cd0692c856d52c Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 21:16:54 +0000 Subject: [PATCH 038/133] Move pointInside to UIView category --- mathEditor/editor/MTEditableMathLabel+UIView.m | 11 +++++++++++ mathEditor/editor/MTEditableMathLabel.h | 2 ++ mathEditor/editor/MTEditableMathLabel.m | 15 --------------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel+UIView.m b/mathEditor/editor/MTEditableMathLabel+UIView.m index 5bf1675..7b539e1 100644 --- a/mathEditor/editor/MTEditableMathLabel+UIView.m +++ b/mathEditor/editor/MTEditableMathLabel+UIView.m @@ -8,6 +8,7 @@ #if TARGET_OS_IPHONE #import "MTEditableMathLabel.h" +#import "MTCaretView.h" NS_ASSUME_NONNULL_BEGIN @@ -22,6 +23,16 @@ -(void)layoutSubviews [self doLayout]; } +- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event +{ + BOOL inside = [super pointInside:point withEvent:event]; + if (inside) { + return YES; + } + // check if a point is in the caret view. + return [self.caretView pointInside:[self convertPoint:point toView:self.caretView] withEvent:event]; +} + @end NS_ASSUME_NONNULL_END diff --git a/mathEditor/editor/MTEditableMathLabel.h b/mathEditor/editor/MTEditableMathLabel.h index 0ce4293..35bb11d 100644 --- a/mathEditor/editor/MTEditableMathLabel.h +++ b/mathEditor/editor/MTEditableMathLabel.h @@ -15,6 +15,7 @@ @class MTEditableMathLabel; @class MTMathListIndex; @class MTCancelView; +@class MTCaretView; /** Delegate for the `MTEditableMathLabel`. All methods are optional. */ @protocol MTEditableMathLabelDelegate @@ -72,6 +73,7 @@ @property (nonatomic) MTColor* caretColor; @property (nonatomic) MTCancelView* cancelImage; +@property (nonatomic) MTCaretView* caretView; @property (nonatomic, weak) id delegate; @property (nonatomic, weak) MTView* keyboard; @property (nonatomic) CGFloat fontSize; diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index b46c026..bedebac 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -38,7 +38,6 @@ @interface MTEditableMathLabel() @end @implementation MTEditableMathLabel { - MTCaretView* _caretView; MTMathListIndex* _insertionIndex; CGAffineTransform _flipTransform; NSMutableArray* _indicesToHighlight; @@ -837,20 +836,6 @@ - (CGPoint)caretRectForIndex:(MTMathListIndex *)index return [self.label.displayList caretPositionForIndex:index]; } -#if TARGET_OS_IPHONE - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - BOOL inside = [super pointInside:point withEvent:event]; - if (inside) { - return YES; - } - // check if a point is in the caret view. - return [_caretView pointInside:[self convertPoint:point toView:_caretView] withEvent:event]; -} - -#endif // TARGET_OS_IPHONE - #pragma mark - Highlighting - (void)highlightCharacterAtIndex:(MTMathListIndex *)index From 4ca139e1f50b92e3ab4209d91dbff1709eeaedf9 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 21:37:27 +0000 Subject: [PATCH 039/133] Simplify --- mathEditor/editor/MTEditableMathLabel.m | 2 +- mathEditor/internal/MTCancelView.m | 9 ++------- mathEditor/internal/MTTapGestureRecognizer.h | 12 ------------ 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index bedebac..999619b 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -216,7 +216,7 @@ - (void) startEditing */ - (void)tap:(MTTapGestureRecognizer *)tap { - [self handleTapAtPoint:MTTapGestureLocationInView(tap, self)]; + [self handleTapAtPoint:[tap locationInView:self]]; } - (void)handleTapAtPoint:(CGPoint)tapPoint diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m index 52c445f..8238041 100644 --- a/mathEditor/internal/MTCancelView.m +++ b/mathEditor/internal/MTCancelView.m @@ -25,10 +25,8 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action self = [super initWithFrame:CGRectZero]; if (self) { #if TARGET_OS_IPHONE - UIImage *image = [UIImage systemImageNamed:@"xmark.circle"]; - if (image != nil) { - image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - } + UIImage *image = [UIImage systemImageNamed:@"clear"]; + image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; _imageView = [[UIImageView alloc] initWithImage:image]; _imageView.contentMode = UIViewContentModeScaleAspectFit; _imageView.tintColor = [MTColor secondaryLabelColor]; @@ -42,9 +40,6 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action [self addSubview:_imageView]; [_imageView pinToSuperview]; -#if TARGET_OS_IPHONE - self.userInteractionEnabled = YES; -#endif MTTapGestureRecognizer *tapRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:target action:action]; [self addGestureRecognizer:tapRecognizer]; self.hidden = YES; diff --git a/mathEditor/internal/MTTapGestureRecognizer.h b/mathEditor/internal/MTTapGestureRecognizer.h index c58b1df..ff47ffb 100644 --- a/mathEditor/internal/MTTapGestureRecognizer.h +++ b/mathEditor/internal/MTTapGestureRecognizer.h @@ -9,23 +9,11 @@ #if TARGET_OS_IPHONE #import - #define MTTapGestureRecognizer UITapGestureRecognizer -static inline CGPoint MTTapGestureLocationInView(MTTapGestureRecognizer *gesture, MTView *view) -{ - return [gesture locationInView:view]; -} - #else #import - #define MTTapGestureRecognizer NSClickGestureRecognizer -static inline CGPoint MTTapGestureLocationInView(MTTapGestureRecognizer *gesture, MTView *view) -{ - return [gesture locationInView:view]; -} - #endif From 6a508c8f19aaaa83870dde4a8d19cad025159f0f Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 21:57:38 +0000 Subject: [PATCH 040/133] Draw CaretHandle --- .../ContentView.swift | 2 + mathEditor/internal/MTCancelView.m | 2 +- mathEditor/internal/MTCaretView.m | 44 ++++++++++++------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index a528faf..29f87f3 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -43,6 +43,8 @@ struct ContentView: View { func makeNSView(context: Context) -> MTEditableMathLabel { let mathLabel = MTEditableMathLabel() mathLabel.backgroundColor = .clear + mathLabel.caretColor = NSColor.labelColor + mathLabel.textColor = NSColor.labelColor // mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance(); return mathLabel } diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m index 8238041..9d40ec9 100644 --- a/mathEditor/internal/MTCancelView.m +++ b/mathEditor/internal/MTCancelView.m @@ -25,7 +25,7 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action self = [super initWithFrame:CGRectZero]; if (self) { #if TARGET_OS_IPHONE - UIImage *image = [UIImage systemImageNamed:@"clear"]; + UIImage *image = [UIImage systemImageNamed:@"xmark.circle"]; image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; _imageView = [[UIImageView alloc] initWithImage:image]; _imageView.contentMode = UIViewContentModeScaleAspectFit; diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index a109b41..a3c5bd8 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -12,6 +12,7 @@ #import "MTEditableMathLabel.h" #import "MTConfig.h" #import "MTView/MTView+Layout.h" +#import "NSBezierPath+addLineToPoint.h" static const NSTimeInterval InitialBlinkDelay = 0.7; static const NSTimeInterval BlinkRate = 0.5; @@ -35,10 +36,8 @@ @interface MTCaretHandle : MTView @end -#if TARGET_OS_IPHONE - @implementation MTCaretHandle { - UIBezierPath* _path; + MTBezierPath* _path; MTColor* _color; } @@ -51,9 +50,9 @@ - (id) initWithFrame:(CGRect)frame return self; } -- (UIBezierPath*) createHandlePath +- (MTBezierPath*) createHandlePath { - UIBezierPath* path = [UIBezierPath bezierPath]; + MTBezierPath* path = [MTBezierPath bezierPath]; CGSize size = self.bounds.size; [path moveToPoint:CGPointMake(size.width/2, 0)]; [path addLineToPoint:CGPointMake(size.width, size.height/4)]; @@ -64,11 +63,6 @@ - (UIBezierPath*) createHandlePath return path; } -- (void) layoutSubviews -{ - _path = [self createHandlePath]; -} - - (void)drawRect:(CGRect)rect { [_color setFill]; @@ -77,7 +71,15 @@ - (void)drawRect:(CGRect)rect - (void) setColor:(MTColor*) color { - _color = [color colorWithAlphaComponent:0.6]; + _color = [color colorWithAlphaComponent:0.7]; +} + +#if TARGET_OS_IPHONE + +- (void) layoutSubviews +{ + [super layoutSubviews]; + _path = [self createHandlePath]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event @@ -113,9 +115,23 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event return CGRectContainsPoint(hitArea, point); } +#endif + + +#if TARGET_OS_OSX +- (void) layout +{ + [super layout]; + _path = [self createHandlePath]; +} + +- (BOOL) isFlipped { + return YES; +} +#endif // TARGET_OS_OSX + @end -#endif @interface MTCaretView () @@ -138,13 +154,11 @@ - (id)initWithEditor:(MTEditableMathLabel*)label _blinker = [[MTView alloc] initWithFrame:CGRectZero]; _blinker.backgroundColor = self.caretColor; [self addSubview:_blinker]; -#if TARGET_OS_IPHONE _handle = [[MTCaretHandle alloc] initWithFrame:CGRectMake(0, 0, kCaretHandleWidth * _scale, kCaretHandleHeight *_scale)]; _handle.backgroundColor = [MTColor clearColor]; _handle.hidden = YES; _handle.label = label; [self addSubview:_handle]; -#endif } return self; } @@ -211,9 +225,7 @@ - (void)dealloc - (void)setCaretColor:(MTColor *)caretColor { _caretColor = caretColor; -#if TARGET_OS_IPHONE _handle.color = caretColor; -#endif _blinker.backgroundColor = self.caretColor; } From fd2fbbe4b7bce53dfea0ab9f0e341370e38208b7 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Fri, 20 Mar 2026 22:09:49 +0000 Subject: [PATCH 041/133] CaretHandle changes when grabbing --- mathEditor/internal/MTCaretView.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index a3c5bd8..30e5c39 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -84,11 +84,14 @@ - (void) layoutSubviews - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + _color = [_color colorWithAlphaComponent:1.0]; + [self setNeedsDisplay]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - + _color = [_color colorWithAlphaComponent:0.6]; + [self setNeedsDisplay]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event @@ -104,7 +107,8 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - + _color = [_color colorWithAlphaComponent:0.6]; + [self setNeedsDisplay]; } - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event From 918891036b822072010da39e0f3420826fc6f20f Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Sat, 21 Mar 2026 01:28:16 +0000 Subject: [PATCH 042/133] Handle mouse events in CaretHandle --- .../editor/MTEditableMathLabel+NSView.m | 5 ++ mathEditor/internal/MTCaretView.m | 77 +++++++++++++++++-- mathEditor/internal/MTView/MTView+HitTest.h | 22 ++++++ mathEditor/internal/MTView/MTView+HitTest.m | 38 +++++++++ 4 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 mathEditor/internal/MTView/MTView+HitTest.h create mode 100644 mathEditor/internal/MTView/MTView+HitTest.m diff --git a/mathEditor/editor/MTEditableMathLabel+NSView.m b/mathEditor/editor/MTEditableMathLabel+NSView.m index 8b93eaa..b78c355 100644 --- a/mathEditor/editor/MTEditableMathLabel+NSView.m +++ b/mathEditor/editor/MTEditableMathLabel+NSView.m @@ -8,6 +8,7 @@ #if TARGET_OS_OSX #import "MTEditableMathLabel.h" +#import "MTView/MTView+HitTest.h" NS_ASSUME_NONNULL_BEGIN @@ -25,6 +26,10 @@ - (BOOL)isFlipped { return YES; } +- (nullable NSView *)hitTest:(NSPoint)point { + return [self hitTestOutsideBounds:point]; +} + @end NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index 30e5c39..9a04d5f 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -12,6 +12,7 @@ #import "MTEditableMathLabel.h" #import "MTConfig.h" #import "MTView/MTView+Layout.h" +#import "MTView/MTView+HitTest.h" #import "NSBezierPath+addLineToPoint.h" static const NSTimeInterval InitialBlinkDelay = 0.7; @@ -74,6 +75,18 @@ - (void) setColor:(MTColor*) color _color = [color colorWithAlphaComponent:0.7]; } +- (void)interactionBegan +{ + _color = [_color colorWithAlphaComponent:1.0]; + [self setNeedsDisplay]; +} + +- (void)interactionEnded +{ + _color = [_color colorWithAlphaComponent:0.6]; + [self setNeedsDisplay]; +} + #if TARGET_OS_IPHONE - (void) layoutSubviews @@ -84,14 +97,12 @@ - (void) layoutSubviews - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - _color = [_color colorWithAlphaComponent:1.0]; - [self setNeedsDisplay]; + [self interactionBegan]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - _color = [_color colorWithAlphaComponent:0.6]; - [self setNeedsDisplay]; + [self interactionEnded]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event @@ -107,8 +118,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - _color = [_color colorWithAlphaComponent:0.6]; - [self setNeedsDisplay]; + [self interactionEnded]; } - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event @@ -132,6 +142,55 @@ - (void) layout - (BOOL) isFlipped { return YES; } + +- (BOOL)acceptsFirstMouse:(NSEvent *)event +{ + return YES; +} + +- (void)mouseDown:(NSEvent *)event +{ + [self interactionBegan]; +} + +- (void)mouseDragged:(NSEvent *)event +{ + NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; + NSRect frame = self.frame; + NSPoint caretPoint = NSMakePoint(loc.x, loc.y - frame.origin.y); + NSPoint labelPoint = [_label convertPoint:caretPoint fromView:self]; + [_label moveCaretToPoint:labelPoint]; +} + +- (void)mouseUp:(NSEvent *)event +{ + [self interactionEnded]; +} + +- (void)mouseCancelled:(NSEvent *)event { + [self interactionEnded]; +} + +- (NSView *)hitTest:(NSPoint)point +{ + if (self.hidden) { + return nil; + } + // Convert point from superview coordinates to local coordinates + NSPoint localPoint = [self convertPoint:point fromView:self.superview]; + // Create a hit area around the center + NSSize size = self.bounds.size; + NSRect hitArea = NSMakeRect((size.width - kCaretHandleHitAreaSize) / 2, + (size.height - kCaretHandleHitAreaSize) / 2, + kCaretHandleHitAreaSize, + kCaretHandleHitAreaSize); + + if (NSPointInRect(localPoint, hitArea)) { + return self; + } + return nil; +} + #endif // TARGET_OS_OSX @end @@ -262,7 +321,11 @@ - (void) layout { } - (BOOL)isFlipped { - return YES; + return YES; +} + +- (NSView *)hitTest:(NSPoint)point { + return [self hitTestOutsideBounds:point]; } #endif // TARGET_OS_OSX diff --git a/mathEditor/internal/MTView/MTView+HitTest.h b/mathEditor/internal/MTView/MTView+HitTest.h new file mode 100644 index 0000000..53c9dc4 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+HitTest.h @@ -0,0 +1,22 @@ +// +// MTView+HitTest.h +// MathEditor +// +// Created by Madiyar Aitbayev on 21/03/2026. +// + +#if TARGET_OS_OSX + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSView (HitTest) + +- (NSView *)hitTestOutsideBounds:(NSPoint)point; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/mathEditor/internal/MTView/MTView+HitTest.m b/mathEditor/internal/MTView/MTView+HitTest.m new file mode 100644 index 0000000..2c5255b --- /dev/null +++ b/mathEditor/internal/MTView/MTView+HitTest.m @@ -0,0 +1,38 @@ +// +// MTView+HitTest.m +// MathEditor +// +// Created by Madiyar Aitbayev on 21/03/2026. +// + +#import "MTView+HitTest.h" + +#if TARGET_OS_OSX + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSView (HitTest) + +- (NSView *)hitTestOutsideBounds:(NSPoint)point +{ + if (self.hidden) { + return nil; + } + NSPoint localPoint = [self convertPoint:point fromView:self.superview]; + for (NSView *child in [self.subviews reverseObjectEnumerator]) { + NSView *hitView = [child hitTest:localPoint]; + if (hitView) { + return hitView; + } + } + if (NSPointInRect(localPoint, self.bounds)) { + return self; + } + return nil; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX From 8a13a62889976a485a809b86dcc990765011feb9 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Sat, 21 Mar 2026 01:50:32 +0000 Subject: [PATCH 043/133] Fix overflowing --- .../ContentView.swift | 20 ++++++++++++++----- .../internal/MTView/MTView+AutoLayout.h | 2 ++ .../internal/MTView/MTView+AutoLayout.m | 15 ++++++++++---- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index 29f87f3..7c1d374 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -6,7 +6,6 @@ import SwiftUI struct ContentView: View { var body: some View { MathEditorView() - .background(Color.gray.opacity(0.1)) .frame(maxHeight: 100) .padding() } @@ -38,18 +37,29 @@ struct ContentView: View { #if os(macOS) struct MathEditorView: NSViewRepresentable { - typealias UIViewType = MTEditableMathLabel + typealias UIViewType = NSView - func makeNSView(context: Context) -> MTEditableMathLabel { + func makeNSView(context: Context) -> NSView { let mathLabel = MTEditableMathLabel() mathLabel.backgroundColor = .clear mathLabel.caretColor = NSColor.labelColor mathLabel.textColor = NSColor.labelColor // mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance(); - return mathLabel + + let wrapper = NSView() + wrapper.addSubview(mathLabel) + mathLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + mathLabel.topAnchor.constraint(equalTo: wrapper.topAnchor), + mathLabel.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor), + mathLabel.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor), + mathLabel.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor, constant: -44), + ]) + wrapper.backgroundColor = .clear + return wrapper } - func updateNSView(_ uiView: MTEditableMathLabel, context: Context) { + func updateNSView(_ nsView: NSView, context: Context) { } } diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.h b/mathEditor/internal/MTView/MTView+AutoLayout.h index dddb124..62cf81b 100644 --- a/mathEditor/internal/MTView/MTView+AutoLayout.h +++ b/mathEditor/internal/MTView/MTView+AutoLayout.h @@ -13,6 +13,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)pinToSuperview; +- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing; + @end NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.m b/mathEditor/internal/MTView/MTView+AutoLayout.m index 85c96c9..be85c28 100644 --- a/mathEditor/internal/MTView/MTView+AutoLayout.m +++ b/mathEditor/internal/MTView/MTView+AutoLayout.m @@ -11,14 +11,21 @@ @implementation MXView (AutoLayout) - (void)pinToSuperview +{ + [self pinToSuperviewWithTop:0 leading:0 bottom:0 trailing:0]; +} + +- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing { MTView *superview = self.superview; + if (!superview) + return; self.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ - [self.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor], - [self.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor], - [self.topAnchor constraintEqualToAnchor:superview.topAnchor], - [self.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor] + [self.topAnchor constraintEqualToAnchor:superview.topAnchor constant:top], + [self.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor constant:leading], + [self.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor constant:-trailing], + [self.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor constant:-bottom] ]]; } From 3e1afa2637cf8fc298c76df29a5199612ff80934 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Sat, 21 Mar 2026 04:18:05 +0000 Subject: [PATCH 044/133] Refactor --- mathEditor/internal/MTCaretView.m | 49 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index 9a04d5f..1e7b041 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -87,6 +87,23 @@ - (void)interactionEnded [self setNeedsDisplay]; } +- (void)handleDragAtLocalPoint:(CGPoint)loc +{ + CGPoint caretPoint = CGPointMake(loc.x, loc.y - self.frame.origin.y); + CGPoint labelPoint = [_label convertPoint:caretPoint fromView:self]; + [_label moveCaretToPoint:labelPoint]; // puts the point at the top to the top of the current caret +} + +- (CGRect)hitArea +{ + // Create a hit area around the center. + CGSize size = self.bounds.size; + return CGRectMake((size.width - kCaretHandleHitAreaSize) / 2, + (size.height - kCaretHandleHitAreaSize) / 2, + kCaretHandleHitAreaSize, + kCaretHandleHitAreaSize); +} + #if TARGET_OS_IPHONE - (void) layoutSubviews @@ -109,11 +126,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // From apple documentation UITouch *aTouch = [touches anyObject]; - CGPoint loc = [aTouch locationInView:self]; - CGRect frame = self.frame; - CGPoint caretPoint = CGPointMake(loc.x, loc.y - frame.origin.y); // puts the point at the top to the top of the current caret - CGPoint labelPoint = [_label convertPoint:caretPoint fromView:self]; - [_label moveCaretToPoint:labelPoint]; + [self handleDragAtLocalPoint:[aTouch locationInView:self]]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event @@ -123,10 +136,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { - // Create a hit area around the center. - CGSize size = self.bounds.size; - CGRect hitArea = CGRectMake((size.width - kCaretHandleHitAreaSize)/2, (size.height - kCaretHandleHitAreaSize)/2, kCaretHandleHitAreaSize, kCaretHandleHitAreaSize); - return CGRectContainsPoint(hitArea, point); + return CGRectContainsPoint([self hitArea], point); } #endif @@ -155,11 +165,8 @@ - (void)mouseDown:(NSEvent *)event - (void)mouseDragged:(NSEvent *)event { - NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; - NSRect frame = self.frame; - NSPoint caretPoint = NSMakePoint(loc.x, loc.y - frame.origin.y); - NSPoint labelPoint = [_label convertPoint:caretPoint fromView:self]; - [_label moveCaretToPoint:labelPoint]; + NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; + [self handleDragAtLocalPoint:point]; } - (void)mouseUp:(NSEvent *)event @@ -174,18 +181,10 @@ - (void)mouseCancelled:(NSEvent *)event { - (NSView *)hitTest:(NSPoint)point { if (self.hidden) { - return nil; + return nil; } - // Convert point from superview coordinates to local coordinates - NSPoint localPoint = [self convertPoint:point fromView:self.superview]; - // Create a hit area around the center - NSSize size = self.bounds.size; - NSRect hitArea = NSMakeRect((size.width - kCaretHandleHitAreaSize) / 2, - (size.height - kCaretHandleHitAreaSize) / 2, - kCaretHandleHitAreaSize, - kCaretHandleHitAreaSize); - - if (NSPointInRect(localPoint, hitArea)) { + CGPoint localPoint = [self convertPoint:point fromView:self.superview]; + if (CGRectContainsPoint([self hitArea], localPoint)) { return self; } return nil; From e63bc06c017bfd554918a8033ef536791ab84dea Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Sat, 21 Mar 2026 04:24:47 +0000 Subject: [PATCH 045/133] Add TODO --- mathEditor/editor/MTEditableMathLabel+NSView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/mathEditor/editor/MTEditableMathLabel+NSView.m b/mathEditor/editor/MTEditableMathLabel+NSView.m index b78c355..3436697 100644 --- a/mathEditor/editor/MTEditableMathLabel+NSView.m +++ b/mathEditor/editor/MTEditableMathLabel+NSView.m @@ -27,6 +27,7 @@ - (BOOL)isFlipped { } - (nullable NSView *)hitTest:(NSPoint)point { + // Ignore `MTMathUILabel`? return [self hitTestOutsideBounds:point]; } From 56ec1a933e9587ee3ecfd463dddd864a6b5c3cb7 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Sat, 21 Mar 2026 12:42:51 +0000 Subject: [PATCH 046/133] Minor update --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- Package.resolved | 2 +- mathEditor/editor/MTEditableMathLabel.m | 9 ++------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2efe63d..d594318 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3845228a2da848133f1c5b0fe02201c5631261188fc4a1a940a392278f808293", + "originHash" : "df491f6bdc78add6bce6eb3401a45b660dc8096d349ea94028db165e9a15431e", "pins" : [ { "identity" : "iosmath", @@ -7,7 +7,7 @@ "location" : "https://github.com/maitbayev/iosMath.git", "state" : { "branch" : "master", - "revision" : "066ba2f8353782a644889efe9ceb884ea844180b" + "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836" } } ], diff --git a/Package.resolved b/Package.resolved index 3ea8321..24b9309 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,7 +6,7 @@ "location" : "https://github.com/maitbayev/iosMath.git", "state" : { "branch" : "master", - "revision" : "01ca7209675caa2bb899cfa8d13f94f910657254" + "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836" } } ], diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 999619b..d0ba437 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -26,11 +26,6 @@ #import "MTUnicode.h" #import "MTMathListBuilder.h" -// TODO: move this method declaration to iosMath. -@interface MTMathUILabel (SizeThatFits) -- (CGSize)sizeThatFits:(CGSize)size; -@end - @interface MTEditableMathLabel() @property (nonatomic) MTMathUILabel* label; @@ -176,7 +171,7 @@ - (CGSize) mathDisplaySize #pragma mark - Custom user interaction -- (BOOL)doBecomeFirstResponder +- (void)doBecomeFirstResponder { if (_insertionIndex == nil) { _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count]; @@ -194,7 +189,7 @@ - (BOOL)doBecomeFirstResponder UIResponder protocol override. Called when our view is being asked to resign first responder state. */ -- (BOOL)doResignFirstResponder +- (void)doResignFirstResponder { [self.keyboard finishedEditing:self]; [self insertionPointChanged]; From bbd646f615bd2a0176e11641edb99389ae6bc06d Mon Sep 17 00:00:00 2001 From: Madiyar Date: Sat, 21 Mar 2026 13:20:59 +0000 Subject: [PATCH 047/133] Improvet hit testing --- mathEditor/editor/MTEditableMathLabel+NSView.m | 8 ++++++-- mathEditor/internal/MTView/MTView+HitTest.h | 1 + mathEditor/internal/MTView/MTView+HitTest.m | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel+NSView.m b/mathEditor/editor/MTEditableMathLabel+NSView.m index 3436697..9bfa91e 100644 --- a/mathEditor/editor/MTEditableMathLabel+NSView.m +++ b/mathEditor/editor/MTEditableMathLabel+NSView.m @@ -8,6 +8,7 @@ #if TARGET_OS_OSX #import "MTEditableMathLabel.h" +#import "MTMathUILabel.h" #import "MTView/MTView+HitTest.h" NS_ASSUME_NONNULL_BEGIN @@ -15,6 +16,10 @@ @interface MTEditableMathLabel (NSView) @end +@interface MTEditableMathLabel () +@property (nonatomic) MTMathUILabel *label; +@end + @implementation MTEditableMathLabel(NSView) - (void)layout { @@ -27,8 +32,7 @@ - (BOOL)isFlipped { } - (nullable NSView *)hitTest:(NSPoint)point { - // Ignore `MTMathUILabel`? - return [self hitTestOutsideBounds:point]; + return [self hitTestOutsideBounds:point ignoringSubviews:@[self.label]]; } @end diff --git a/mathEditor/internal/MTView/MTView+HitTest.h b/mathEditor/internal/MTView/MTView+HitTest.h index 53c9dc4..0c87d63 100644 --- a/mathEditor/internal/MTView/MTView+HitTest.h +++ b/mathEditor/internal/MTView/MTView+HitTest.h @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN @interface NSView (HitTest) - (NSView *)hitTestOutsideBounds:(NSPoint)point; +- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews; @end diff --git a/mathEditor/internal/MTView/MTView+HitTest.m b/mathEditor/internal/MTView/MTView+HitTest.m index 2c5255b..59822b5 100644 --- a/mathEditor/internal/MTView/MTView+HitTest.m +++ b/mathEditor/internal/MTView/MTView+HitTest.m @@ -14,12 +14,20 @@ @implementation NSView (HitTest) - (NSView *)hitTestOutsideBounds:(NSPoint)point +{ + return [self hitTestOutsideBounds:point ignoringSubviews:@[]]; +} + +- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews { if (self.hidden) { return nil; } NSPoint localPoint = [self convertPoint:point fromView:self.superview]; for (NSView *child in [self.subviews reverseObjectEnumerator]) { + if ([ignoredSubviews containsObject:child]) { + continue; + } NSView *hitView = [child hitTest:localPoint]; if (hitView) { return hitView; From fd758532faaf5c6ab7bdc2d3e1ee7a6b56a508bf Mon Sep 17 00:00:00 2001 From: Madiyar Date: Sat, 21 Mar 2026 13:27:26 +0000 Subject: [PATCH 048/133] Handle becomeFirstResponder correctly on MacOS --- mathEditor/editor/MTEditableMathLabel.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index d0ba437..93a1bd4 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -202,7 +202,11 @@ - (void) startEditing { if (![self isFirstResponder]) { // Become first responder state (which shows software keyboard, if applicable). + #if TARGET_OS_OSX + [self.window makeFirstResponder:self]; + #else [self becomeFirstResponder]; + #endif } } From f2bd26e1e75685cb3930aa730c735baefb5dc266 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Sat, 21 Mar 2026 22:06:35 +0000 Subject: [PATCH 049/133] Hide cancelview and caretview --- mathEditor/editor/MTEditableMathLabel.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 93a1bd4..c0c757b 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -87,7 +87,7 @@ - (void) initialize #endif label.textAlignment = kMTTextAlignmentCenter; self.label = label; - [self createCancelImage]; + // [self createCancelImage]; CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height); _flipTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, -1.0), transform); @@ -230,7 +230,7 @@ - (void)handleTapAtPoint:(CGPoint)tapPoint if (_insertionIndex == nil) { _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count]; } - [_caretView showHandle:YES]; + [_caretView showHandle:NO]; [self insertionPointChanged]; } } From da49570993b59f72da7890b1ec664d630ea1bf1c Mon Sep 17 00:00:00 2001 From: Madiyar Date: Sat, 21 Mar 2026 22:17:51 +0000 Subject: [PATCH 050/133] Initial SwiftUI keyboard --- .../ContentView.swift | 3 +- Package.swift | 8 + mathKeyboard/include/module.modulemap | 1 + .../MTMathKeyboardSwiftUIRootView.swift | 282 ++++++++++++++++++ 4 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index 7c1d374..4ef911f 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -17,6 +17,7 @@ struct ContentView: View { #if os(iOS) import MathKeyboard + import MathKeyboardSwiftUI struct MathEditorView: UIViewRepresentable { typealias UIViewType = MTEditableMathLabel @@ -24,7 +25,7 @@ struct ContentView: View { func makeUIView(context: Context) -> MTEditableMathLabel { let mathLabel = MTEditableMathLabel() mathLabel.backgroundColor = .clear - mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance() + mathLabel.keyboard = MTMathKeyboardSwiftUIRootView.sharedInstance() return mathLabel } diff --git a/Package.swift b/Package.swift index 9f1716e..4f78dbc 100644 --- a/Package.swift +++ b/Package.swift @@ -13,6 +13,9 @@ let package = Package( .library( name: "MathKeyboard", targets: ["MathKeyboard"]), + .library( + name: "MathKeyboardSwiftUI", + targets: ["MathKeyboardSwiftUI"]), ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") @@ -36,6 +39,11 @@ let package = Package( .headerSearchPath("./keyboard") ] ), + .target( + name: "MathKeyboardSwiftUI", + dependencies: ["MathKeyboard", "MathEditor"], + path: "./mathKeyboardSwiftUI" + ), .testTarget( name: "MathEditorTests", dependencies: ["MathEditor"], diff --git a/mathKeyboard/include/module.modulemap b/mathKeyboard/include/module.modulemap index c1a74ac..ca67926 100644 --- a/mathKeyboard/include/module.modulemap +++ b/mathKeyboard/include/module.modulemap @@ -1,5 +1,6 @@ module MathKeyboard { header "../keyboard/MTMathKeyboardRootView.h" + header "../keyboard/MTKeyboard.h" export * } diff --git a/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift new file mode 100644 index 0000000..fe8ef7e --- /dev/null +++ b/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -0,0 +1,282 @@ +#if os(iOS) + + import MathEditor + import MathKeyboard + import SwiftUI + import UIKit + + @objcMembers + public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboard { + fileprivate static let defaultSize = CGSize(width: 320, height: 225) + private static let defaultTab: KeyboardTab = .numbers + private static let shared = MTMathKeyboardSwiftUIRootView( + frame: CGRect(origin: .zero, size: defaultSize) + ) + + private let controller = KeyboardController() + private let hostingController: UIHostingController + + public override init(frame: CGRect) { + hostingController = UIHostingController( + rootView: KeyboardRootContentView(controller: controller) + ) + super.init(frame: frame) + commonInit() + } + + public required init?(coder: NSCoder) { + hostingController = UIHostingController( + rootView: KeyboardRootContentView(controller: controller) + ) + super.init(coder: coder) + commonInit() + } + + public static func sharedInstance() -> MTMathKeyboardSwiftUIRootView { + shared + } + + public func switchToDefaultTab() { + controller.currentTab = Self.defaultTab + } + + public var equalsAllowed: Bool { + get { controller.equalsAllowed } + set { controller.equalsAllowed = newValue } + } + + public var fractionsAllowed: Bool { + get { controller.fractionsAllowed } + set { controller.fractionsAllowed = newValue } + } + + public var variablesAllowed: Bool { + get { controller.variablesAllowed } + set { controller.variablesAllowed = newValue } + } + + public var numbersAllowed: Bool { + get { controller.numbersAllowed } + set { controller.numbersAllowed = newValue } + } + + public var operatorsAllowed: Bool { + get { controller.operatorsAllowed } + set { controller.operatorsAllowed = newValue } + } + + public var exponentHighlighted: Bool { + get { controller.exponentHighlighted } + set { controller.exponentHighlighted = newValue } + } + + public var squareRootHighlighted: Bool { + get { controller.squareRootHighlighted } + set { controller.squareRootHighlighted = newValue } + } + + public var radicalHighlighted: Bool { + get { controller.radicalHighlighted } + set { controller.radicalHighlighted = newValue } + } + + public func startedEditing(_ label: (any UIView & UIKeyInput)!) { + controller.startedEditing(label) + } + + public func finishedEditing(_ label: (any UIView & UIKeyInput)!) { + controller.finishedEditing() + } + + private func commonInit() { + backgroundColor = .white + autoresizingMask = [.flexibleWidth, .flexibleHeight] + + let hostedView = hostingController.view! + if #available(iOS 16.4, *) { + hostingController.safeAreaRegions = [] + } + hostedView.backgroundColor = .clear + hostedView.translatesAutoresizingMaskIntoConstraints = false + addSubview(hostedView) + + NSLayoutConstraint.activate([ + hostedView.topAnchor.constraint(equalTo: topAnchor), + hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), + hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), + hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + } + + private enum KeyboardTab: Int, CaseIterable { + case numbers + case operations + case functions + case letters + + var imageNames: (normal: String, selected: String) { + switch self { + case .numbers: return ("Numbers Symbol wbg", "Number Symbol") + case .operations: return ("Operations Symbol wbg", "Operations Symbol") + case .functions: return ("Functions Symbol wbg", "Functions Symbol") + case .letters: return ("Letter Symbol wbg", "Letter Symbol") + } + } + } + + private final class KeyboardController: ObservableObject { + @Published var currentTab: KeyboardTab = .numbers + + var equalsAllowed = false { didSet { forEachKeyboard { $0.setEqualsState(equalsAllowed) } } } + var fractionsAllowed = false { + didSet { forEachKeyboard { $0.setFractionState(fractionsAllowed) } } + } + var variablesAllowed = false { + didSet { forEachKeyboard { $0.setVariablesState(variablesAllowed) } } + } + var numbersAllowed = false { didSet { forEachKeyboard { $0.setNumbersState(numbersAllowed) } } } + var operatorsAllowed = false { + didSet { forEachKeyboard { $0.setOperatorState(operatorsAllowed) } } + } + var exponentHighlighted = false { + didSet { forEachKeyboard { $0.setExponentState(exponentHighlighted) } } + } + var squareRootHighlighted = false { + didSet { forEachKeyboard { $0.setSquareRootState(squareRootHighlighted) } } + } + var radicalHighlighted = false { + didSet { forEachKeyboard { $0.setRadicalState(radicalHighlighted) } } + } + + private lazy var keyboards: [KeyboardTab: MTKeyboard] = Dictionary( + uniqueKeysWithValues: KeyboardTab.allCases.map { tab in + (tab, makeKeyboard(named: tab.nibName)) + } + ) + + func keyboard(for tab: KeyboardTab) -> MTKeyboard { + keyboards[tab]! + } + + func startedEditing(_ label: (any UIView & UIKeyInput)?) { + forEachKeyboard { $0.textView = label } + } + + func finishedEditing() { + forEachKeyboard { $0.textView = nil } + } + + private func makeKeyboard(named nibName: String) -> MTKeyboard { + let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() + let keyboard = UINib(nibName: nibName, bundle: bundle) + .instantiate(withOwner: nil, options: nil) + .compactMap { $0 as? MTKeyboard } + .first! + keyboard.translatesAutoresizingMaskIntoConstraints = false + return keyboard + } + + private func forEachKeyboard(_ body: (MTKeyboard) -> Void) { + keyboards.values.forEach(body) + } + } + + extension KeyboardTab { + fileprivate var nibName: String { + switch self { + case .numbers: return "MTKeyboard" + case .operations: return "MTKeyboardTab2" + case .functions: return "MTKeyboardTab3" + case .letters: return "MTKeyboardTab4" + } + } + } + + private struct KeyboardRootContentView: View { + @ObservedObject var controller: KeyboardController + + var body: some View { + GeometryReader { proxy in + let totalHeight = max(proxy.size.height, MTMathKeyboardSwiftUIRootView.defaultSize.height) + let tabHeight = totalHeight / 5.0 + let keyboardHeight = totalHeight - tabHeight + + VStack(spacing: 0) { + HStack(spacing: 0) { + ForEach(KeyboardTab.allCases, id: \.rawValue) { tab in + Button { + controller.currentTab = tab + } label: { + Image(uiImage: tabImage(for: tab)) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal, 8) + .padding(.vertical, 6) + } + .buttonStyle(.plain) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(white: 0.768627451)) + } + } + .frame(height: tabHeight) + + KeyboardContainerRepresentable(keyboard: controller.keyboard(for: controller.currentTab)) + .frame(height: keyboardHeight) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .background(Color.white) + .edgesIgnoringSafeArea(.all) + } + } + + private func tabImage(for tab: KeyboardTab) -> UIImage { + let names = tab.imageNames + let name = controller.currentTab == tab ? names.selected : names.normal + return UIImage( + named: name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) ?? UIImage() + } + } + + private struct KeyboardContainerRepresentable: UIViewRepresentable { + let keyboard: MTKeyboard + + func makeUIView(context: Context) -> KeyboardContainerView { + let view = KeyboardContainerView() + view.display(keyboard: keyboard) + return view + } + + func updateUIView(_ uiView: KeyboardContainerView, context: Context) { + uiView.display(keyboard: keyboard) + } + } + + private final class KeyboardContainerView: UIView { + private weak var currentKeyboard: UIView? + + override var intrinsicContentSize: CGSize { + CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) + } + + func display(keyboard: UIView) { + guard currentKeyboard !== keyboard else { return } + + currentKeyboard?.removeFromSuperview() + currentKeyboard = keyboard + addSubview(keyboard) + + NSLayoutConstraint.activate([ + keyboard.topAnchor.constraint(equalTo: topAnchor), + keyboard.leadingAnchor.constraint(equalTo: leadingAnchor), + keyboard.trailingAnchor.constraint(equalTo: trailingAnchor), + keyboard.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + } +#endif From 7e1533076e807845f8ddf79f4ddea314d898feaa Mon Sep 17 00:00:00 2001 From: Madiyar Date: Sun, 22 Mar 2026 15:05:31 +0000 Subject: [PATCH 051/133] Initial SwiftUI based keyboard --- .../project.pbxproj | 8 + .../KeyboardContainerView.swift | 82 ++++++ mathKeyboardSwiftUI/KeyboardState.swift | 18 ++ mathKeyboardSwiftUI/KeyboardTab.swift | 35 +++ .../MTMathKeyboardSwiftUIRootView.swift | 244 ++++-------------- .../MathKeyboardRootView.swift | 62 +++++ 6 files changed, 250 insertions(+), 199 deletions(-) create mode 100644 mathKeyboardSwiftUI/KeyboardContainerView.swift create mode 100644 mathKeyboardSwiftUI/KeyboardState.swift create mode 100644 mathKeyboardSwiftUI/KeyboardTab.swift create mode 100644 mathKeyboardSwiftUI/MathKeyboardRootView.swift diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj index 533b729..fcb8b72 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28F72E2D525900F9B6FE /* MathEditor */; }; 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28FA2E2D527100F9B6FE /* MathEditor */; }; C1489ED52F6CB33A00AB2993 /* MathKeyboard in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = C1489ED42F6CB33A00AB2993 /* MathKeyboard */; }; + C1DAAE012F70231100E4E983 /* MathKeyboardSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = C1DAAE002F70231100E4E983 /* MathKeyboardSwiftUI */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -30,6 +31,7 @@ buildActionMask = 2147483647; files = ( C1489ED52F6CB33A00AB2993 /* MathKeyboard in Frameworks */, + C1DAAE012F70231100E4E983 /* MathKeyboardSwiftUI in Frameworks */, 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */, 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */, ); @@ -85,6 +87,7 @@ 862D28F72E2D525900F9B6FE /* MathEditor */, 862D28FA2E2D527100F9B6FE /* MathEditor */, C1489ED42F6CB33A00AB2993 /* MathKeyboard */, + C1DAAE002F70231100E4E983 /* MathKeyboardSwiftUI */, ); productName = MathEditorSwiftUIExample; productReference = 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */; @@ -386,6 +389,11 @@ package = 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */; productName = MathKeyboard; }; + C1DAAE002F70231100E4E983 /* MathKeyboardSwiftUI */ = { + isa = XCSwiftPackageProductDependency; + package = 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */; + productName = MathKeyboardSwiftUI; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 862D28DF2E2D51E100F9B6FE /* Project object */; diff --git a/mathKeyboardSwiftUI/KeyboardContainerView.swift b/mathKeyboardSwiftUI/KeyboardContainerView.swift new file mode 100644 index 0000000..1dd4695 --- /dev/null +++ b/mathKeyboardSwiftUI/KeyboardContainerView.swift @@ -0,0 +1,82 @@ +// +// KeyboardContainerView.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 22/03/2026. +// + +import MathEditor +import MathKeyboard +import SwiftUI +import UIKit + +struct KeyboardContainerView: UIViewRepresentable { + let state: KeyboardState + weak var textInput: (any UIView & UIKeyInput)? + + func makeUIView(context: Context) -> KeyboardContainerUIView { + let view = KeyboardContainerUIView() + view.sync( + state: state, + editingTarget: textInput + ) + return view + } + + func updateUIView(_ uiView: KeyboardContainerUIView, context: Context) { + uiView.sync( + state: state, + editingTarget: textInput + ) + } +} + +final class KeyboardContainerUIView: UIView { + private weak var currentKeyboard: UIView? + private lazy var keyboards: [KeyboardTab: MTKeyboard] = Dictionary( + uniqueKeysWithValues: KeyboardTab.allCases.map { tab in + (tab, makeKeyboard(named: tab.nibName)) + } + ) + + fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { + for keyboard in keyboards.values { + keyboard.textView = editingTarget + keyboard.setEqualsState(state.equalsAllowed) + keyboard.setFractionState(state.fractionsAllowed) + keyboard.setVariablesState(state.variablesAllowed) + keyboard.setNumbersState(state.numbersAllowed) + keyboard.setOperatorState(state.operatorsAllowed) + keyboard.setExponentState(state.exponentHighlighted) + keyboard.setSquareRootState(state.squareRootHighlighted) + keyboard.setRadicalState(state.radicalHighlighted) + } + + display(keyboard: keyboards[state.currentTab]!) + } + + fileprivate func display(keyboard: UIView) { + guard currentKeyboard !== keyboard else { return } + + currentKeyboard?.removeFromSuperview() + currentKeyboard = keyboard + addSubview(keyboard) + + NSLayoutConstraint.activate([ + keyboard.topAnchor.constraint(equalTo: topAnchor), + keyboard.leadingAnchor.constraint(equalTo: leadingAnchor), + keyboard.trailingAnchor.constraint(equalTo: trailingAnchor), + keyboard.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + private func makeKeyboard(named nibName: String) -> MTKeyboard { + let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() + let keyboard = UINib(nibName: nibName, bundle: bundle) + .instantiate(withOwner: nil, options: nil) + .compactMap { $0 as? MTKeyboard } + .first! + keyboard.translatesAutoresizingMaskIntoConstraints = false + return keyboard + } +} diff --git a/mathKeyboardSwiftUI/KeyboardState.swift b/mathKeyboardSwiftUI/KeyboardState.swift new file mode 100644 index 0000000..d70b669 --- /dev/null +++ b/mathKeyboardSwiftUI/KeyboardState.swift @@ -0,0 +1,18 @@ +// +// KeyboardState.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 22/03/2026. +// + +struct KeyboardState { + var currentTab: KeyboardTab = .numbers + var equalsAllowed = true + var fractionsAllowed = true + var variablesAllowed = true + var numbersAllowed = true + var operatorsAllowed = true + var exponentHighlighted = false + var squareRootHighlighted = false + var radicalHighlighted = false +} diff --git a/mathKeyboardSwiftUI/KeyboardTab.swift b/mathKeyboardSwiftUI/KeyboardTab.swift new file mode 100644 index 0000000..c2cb3fa --- /dev/null +++ b/mathKeyboardSwiftUI/KeyboardTab.swift @@ -0,0 +1,35 @@ +// +// KeyboardTab.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 22/03/2026. +// + +enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable { + case numbers + case operations + case functions + case letters + + var id: Self { self } +} + +extension KeyboardTab { + var imageNames: (normal: String, selected: String) { + switch self { + case .numbers: return ("Numbers Symbol wbg", "Number Symbol") + case .operations: return ("Operations Symbol wbg", "Operations Symbol") + case .functions: return ("Functions Symbol wbg", "Functions Symbol") + case .letters: return ("Letter Symbol wbg", "Letter Symbol") + } + } + + var nibName: String { + switch self { + case .numbers: return "MTKeyboard" + case .operations: return "MTKeyboardTab2" + case .functions: return "MTKeyboardTab3" + case .letters: return "MTKeyboardTab4" + } + } +} diff --git a/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index fe8ef7e..469e0df 100644 --- a/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -5,29 +5,22 @@ import SwiftUI import UIKit - @objcMembers public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboard { - fileprivate static let defaultSize = CGSize(width: 320, height: 225) private static let defaultTab: KeyboardTab = .numbers - private static let shared = MTMathKeyboardSwiftUIRootView( - frame: CGRect(origin: .zero, size: defaultSize) - ) + private static let shared = MTMathKeyboardSwiftUIRootView() - private let controller = KeyboardController() - private let hostingController: UIHostingController + private var state = KeyboardState() + private weak var textInput: (any UIView & UIKeyInput)? + private lazy var hostingController = UIHostingController( + rootView: makeRootView() + ) public override init(frame: CGRect) { - hostingController = UIHostingController( - rootView: KeyboardRootContentView(controller: controller) - ) super.init(frame: frame) commonInit() } public required init?(coder: NSCoder) { - hostingController = UIHostingController( - rootView: KeyboardRootContentView(controller: controller) - ) super.init(coder: coder) commonInit() } @@ -37,55 +30,72 @@ } public func switchToDefaultTab() { - controller.currentTab = Self.defaultTab + updateState { $0.currentTab = Self.defaultTab } } public var equalsAllowed: Bool { - get { controller.equalsAllowed } - set { controller.equalsAllowed = newValue } + get { state.equalsAllowed } + set { updateState { $0.equalsAllowed = newValue } } } public var fractionsAllowed: Bool { - get { controller.fractionsAllowed } - set { controller.fractionsAllowed = newValue } + get { state.fractionsAllowed } + set { updateState { $0.fractionsAllowed = newValue } } } public var variablesAllowed: Bool { - get { controller.variablesAllowed } - set { controller.variablesAllowed = newValue } + get { state.variablesAllowed } + set { updateState { $0.variablesAllowed = newValue } } } public var numbersAllowed: Bool { - get { controller.numbersAllowed } - set { controller.numbersAllowed = newValue } + get { state.numbersAllowed } + set { updateState { $0.numbersAllowed = newValue } } } public var operatorsAllowed: Bool { - get { controller.operatorsAllowed } - set { controller.operatorsAllowed = newValue } + get { state.operatorsAllowed } + set { updateState { $0.operatorsAllowed = newValue } } } public var exponentHighlighted: Bool { - get { controller.exponentHighlighted } - set { controller.exponentHighlighted = newValue } + get { state.exponentHighlighted } + set { updateState { $0.exponentHighlighted = newValue } } } public var squareRootHighlighted: Bool { - get { controller.squareRootHighlighted } - set { controller.squareRootHighlighted = newValue } + get { state.squareRootHighlighted } + set { updateState { $0.squareRootHighlighted = newValue } } } public var radicalHighlighted: Bool { - get { controller.radicalHighlighted } - set { controller.radicalHighlighted = newValue } + get { state.radicalHighlighted } + set { updateState { $0.radicalHighlighted = newValue } } } public func startedEditing(_ label: (any UIView & UIKeyInput)!) { - controller.startedEditing(label) + textInput = label + updateRootView() } public func finishedEditing(_ label: (any UIView & UIKeyInput)!) { - controller.finishedEditing() + textInput = nil + updateRootView() + } + + private func updateState(_ update: (inout KeyboardState) -> Void) { + update(&state) + updateRootView() + } + + private func makeRootView() -> MathKeyboardRootView { + MathKeyboardRootView( + state: state, + textInput: textInput, + onTabSelected: { [weak self] tab in + self?.updateState { $0.currentTab = tab } + } + ) } private func commonInit() { @@ -106,177 +116,13 @@ hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) - } - } - - private enum KeyboardTab: Int, CaseIterable { - case numbers - case operations - case functions - case letters - - var imageNames: (normal: String, selected: String) { - switch self { - case .numbers: return ("Numbers Symbol wbg", "Number Symbol") - case .operations: return ("Operations Symbol wbg", "Operations Symbol") - case .functions: return ("Functions Symbol wbg", "Functions Symbol") - case .letters: return ("Letter Symbol wbg", "Letter Symbol") - } - } - } - - private final class KeyboardController: ObservableObject { - @Published var currentTab: KeyboardTab = .numbers - - var equalsAllowed = false { didSet { forEachKeyboard { $0.setEqualsState(equalsAllowed) } } } - var fractionsAllowed = false { - didSet { forEachKeyboard { $0.setFractionState(fractionsAllowed) } } - } - var variablesAllowed = false { - didSet { forEachKeyboard { $0.setVariablesState(variablesAllowed) } } - } - var numbersAllowed = false { didSet { forEachKeyboard { $0.setNumbersState(numbersAllowed) } } } - var operatorsAllowed = false { - didSet { forEachKeyboard { $0.setOperatorState(operatorsAllowed) } } - } - var exponentHighlighted = false { - didSet { forEachKeyboard { $0.setExponentState(exponentHighlighted) } } - } - var squareRootHighlighted = false { - didSet { forEachKeyboard { $0.setSquareRootState(squareRootHighlighted) } } - } - var radicalHighlighted = false { - didSet { forEachKeyboard { $0.setRadicalState(radicalHighlighted) } } - } - - private lazy var keyboards: [KeyboardTab: MTKeyboard] = Dictionary( - uniqueKeysWithValues: KeyboardTab.allCases.map { tab in - (tab, makeKeyboard(named: tab.nibName)) - } - ) - - func keyboard(for tab: KeyboardTab) -> MTKeyboard { - keyboards[tab]! - } - - func startedEditing(_ label: (any UIView & UIKeyInput)?) { - forEachKeyboard { $0.textView = label } - } - func finishedEditing() { - forEachKeyboard { $0.textView = nil } + updateRootView() } - private func makeKeyboard(named nibName: String) -> MTKeyboard { - let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() - let keyboard = UINib(nibName: nibName, bundle: bundle) - .instantiate(withOwner: nil, options: nil) - .compactMap { $0 as? MTKeyboard } - .first! - keyboard.translatesAutoresizingMaskIntoConstraints = false - return keyboard - } - - private func forEachKeyboard(_ body: (MTKeyboard) -> Void) { - keyboards.values.forEach(body) - } - } - - extension KeyboardTab { - fileprivate var nibName: String { - switch self { - case .numbers: return "MTKeyboard" - case .operations: return "MTKeyboardTab2" - case .functions: return "MTKeyboardTab3" - case .letters: return "MTKeyboardTab4" - } - } - } - - private struct KeyboardRootContentView: View { - @ObservedObject var controller: KeyboardController - - var body: some View { - GeometryReader { proxy in - let totalHeight = max(proxy.size.height, MTMathKeyboardSwiftUIRootView.defaultSize.height) - let tabHeight = totalHeight / 5.0 - let keyboardHeight = totalHeight - tabHeight - - VStack(spacing: 0) { - HStack(spacing: 0) { - ForEach(KeyboardTab.allCases, id: \.rawValue) { tab in - Button { - controller.currentTab = tab - } label: { - Image(uiImage: tabImage(for: tab)) - .renderingMode(.original) - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(.horizontal, 8) - .padding(.vertical, 6) - } - .buttonStyle(.plain) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(white: 0.768627451)) - } - } - .frame(height: tabHeight) - - KeyboardContainerRepresentable(keyboard: controller.keyboard(for: controller.currentTab)) - .frame(height: keyboardHeight) - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .background(Color.white) - .edgesIgnoringSafeArea(.all) - } - } - - private func tabImage(for tab: KeyboardTab) -> UIImage { - let names = tab.imageNames - let name = controller.currentTab == tab ? names.selected : names.normal - return UIImage( - named: name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) ?? UIImage() + private func updateRootView() { + hostingController.rootView = makeRootView() } } - private struct KeyboardContainerRepresentable: UIViewRepresentable { - let keyboard: MTKeyboard - - func makeUIView(context: Context) -> KeyboardContainerView { - let view = KeyboardContainerView() - view.display(keyboard: keyboard) - return view - } - - func updateUIView(_ uiView: KeyboardContainerView, context: Context) { - uiView.display(keyboard: keyboard) - } - } - - private final class KeyboardContainerView: UIView { - private weak var currentKeyboard: UIView? - - override var intrinsicContentSize: CGSize { - CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) - } - - func display(keyboard: UIView) { - guard currentKeyboard !== keyboard else { return } - - currentKeyboard?.removeFromSuperview() - currentKeyboard = keyboard - addSubview(keyboard) - - NSLayoutConstraint.activate([ - keyboard.topAnchor.constraint(equalTo: topAnchor), - keyboard.leadingAnchor.constraint(equalTo: leadingAnchor), - keyboard.trailingAnchor.constraint(equalTo: trailingAnchor), - keyboard.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - } - } #endif diff --git a/mathKeyboardSwiftUI/MathKeyboardRootView.swift b/mathKeyboardSwiftUI/MathKeyboardRootView.swift new file mode 100644 index 0000000..92637c8 --- /dev/null +++ b/mathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -0,0 +1,62 @@ +// +// MathKeyboardRootView.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 22/03/2026. +// + +import MathKeyboard +import SwiftUI + +public struct MathKeyboardRootView: View { + let state: KeyboardState + weak var textInput: (any UIView & UIKeyInput)? + let onTabSelected: (KeyboardTab) -> Void + + public var body: some View { + GeometryReader { proxy in + let totalHeight = proxy.size.height + let tabHeight = totalHeight / 5.0 + let keyboardHeight = totalHeight - tabHeight + + VStack(spacing: 0) { + HStack(spacing: 0) { + ForEach(KeyboardTab.allCases) { tab in + Button { + onTabSelected(tab) + } label: { + Image(uiImage: tabImage(for: tab)) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal, 8) + .padding(.vertical, 6) + } + .buttonStyle(.plain) + .background(Color(white: 0.768627451)) + } + } + .frame(height: tabHeight) + + KeyboardContainerView( + state: state, + textInput: textInput + ) + .frame(height: keyboardHeight) + } + .background(Color.white) + .edgesIgnoringSafeArea(.all) + } + } + + private func tabImage(for tab: KeyboardTab) -> UIImage { + let names = tab.imageNames + let name = state.currentTab == tab ? names.selected : names.normal + return UIImage( + named: name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) ?? UIImage() + } +} From 0bfc78cb59cfb3d296d2fc04780ac2d78f4d6ca2 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Sun, 22 Mar 2026 15:20:32 +0000 Subject: [PATCH 052/133] nit --- mathKeyboardSwiftUI/KeyboardContainerView.swift | 4 ++++ mathKeyboardSwiftUI/MathKeyboardRootView.swift | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/mathKeyboardSwiftUI/KeyboardContainerView.swift b/mathKeyboardSwiftUI/KeyboardContainerView.swift index 1dd4695..f73ad7c 100644 --- a/mathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/mathKeyboardSwiftUI/KeyboardContainerView.swift @@ -5,6 +5,8 @@ // Created by Madiyar Aitbayev on 22/03/2026. // +#if os(iOS) + import MathEditor import MathKeyboard import SwiftUI @@ -80,3 +82,5 @@ final class KeyboardContainerUIView: UIView { return keyboard } } + +#endif // os(iOS) diff --git a/mathKeyboardSwiftUI/MathKeyboardRootView.swift b/mathKeyboardSwiftUI/MathKeyboardRootView.swift index 92637c8..279dbe1 100644 --- a/mathKeyboardSwiftUI/MathKeyboardRootView.swift +++ b/mathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -8,6 +8,8 @@ import MathKeyboard import SwiftUI +#if os(iOS) + public struct MathKeyboardRootView: View { let state: KeyboardState weak var textInput: (any UIView & UIKeyInput)? @@ -60,3 +62,5 @@ public struct MathKeyboardRootView: View { ) ?? UIImage() } } + +#endif // os(iOS) From 01ff25ad0f5ff7f1594480ec74bcdf9e7b65a581 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 08:59:13 +0000 Subject: [PATCH 053/133] Keyboard numbers in SwiftUI --- .../KeyboardContainerView.swift | 22 +- mathKeyboardSwiftUI/KeyboardTab.swift | 12 +- .../MathKeyboardRootView.swift | 29 +- mathKeyboardSwiftUI/NumbersKeyboardView.swift | 570 ++++++++++++++++++ 4 files changed, 615 insertions(+), 18 deletions(-) create mode 100644 mathKeyboardSwiftUI/NumbersKeyboardView.swift diff --git a/mathKeyboardSwiftUI/KeyboardContainerView.swift b/mathKeyboardSwiftUI/KeyboardContainerView.swift index f73ad7c..d8293ca 100644 --- a/mathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/mathKeyboardSwiftUI/KeyboardContainerView.swift @@ -35,15 +35,17 @@ struct KeyboardContainerView: UIViewRepresentable { final class KeyboardContainerUIView: UIView { private weak var currentKeyboard: UIView? - private lazy var keyboards: [KeyboardTab: MTKeyboard] = Dictionary( - uniqueKeysWithValues: KeyboardTab.allCases.map { tab in - (tab, makeKeyboard(named: tab.nibName)) - } - ) + private lazy var keyboards: [KeyboardTab: (UIView & KeyboardConfigurable)] = [ + .numbers: makeNumbersKeyboard(), + .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), + .operations: makeKeyboard(named: KeyboardTab.operations.nibName), + .functions: makeKeyboard(named: KeyboardTab.functions.nibName), + .letters: makeKeyboard(named: KeyboardTab.letters.nibName), + ] fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { for keyboard in keyboards.values { - keyboard.textView = editingTarget + keyboard.setEditingTarget(editingTarget) keyboard.setEqualsState(state.equalsAllowed) keyboard.setFractionState(state.fractionsAllowed) keyboard.setVariablesState(state.variablesAllowed) @@ -72,7 +74,13 @@ final class KeyboardContainerUIView: UIView { ]) } - private func makeKeyboard(named nibName: String) -> MTKeyboard { + private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { + let keyboard = NumbersKeyboardHostView() + keyboard.translatesAutoresizingMaskIntoConstraints = false + return keyboard + } + + private func makeKeyboard(named nibName: String) -> UIView & KeyboardConfigurable { let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() let keyboard = UINib(nibName: nibName, bundle: bundle) .instantiate(withOwner: nil, options: nil) diff --git a/mathKeyboardSwiftUI/KeyboardTab.swift b/mathKeyboardSwiftUI/KeyboardTab.swift index c2cb3fa..d80e94e 100644 --- a/mathKeyboardSwiftUI/KeyboardTab.swift +++ b/mathKeyboardSwiftUI/KeyboardTab.swift @@ -7,6 +7,7 @@ enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable { case numbers + case legacyNumbers case operations case functions case letters @@ -15,9 +16,10 @@ enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable { } extension KeyboardTab { - var imageNames: (normal: String, selected: String) { + var imageNames: (normal: String, selected: String)? { switch self { case .numbers: return ("Numbers Symbol wbg", "Number Symbol") + case .legacyNumbers: return nil case .operations: return ("Operations Symbol wbg", "Operations Symbol") case .functions: return ("Functions Symbol wbg", "Functions Symbol") case .letters: return ("Letter Symbol wbg", "Letter Symbol") @@ -27,9 +29,17 @@ extension KeyboardTab { var nibName: String { switch self { case .numbers: return "MTKeyboard" + case .legacyNumbers: return "MTKeyboard" case .operations: return "MTKeyboardTab2" case .functions: return "MTKeyboardTab3" case .letters: return "MTKeyboardTab4" } } + + var title: String? { + switch self { + case .legacyNumbers: return "Old" + default: return nil + } + } } diff --git a/mathKeyboardSwiftUI/MathKeyboardRootView.swift b/mathKeyboardSwiftUI/MathKeyboardRootView.swift index 279dbe1..163461a 100644 --- a/mathKeyboardSwiftUI/MathKeyboardRootView.swift +++ b/mathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -27,13 +27,20 @@ public struct MathKeyboardRootView: View { Button { onTabSelected(tab) } label: { - Image(uiImage: tabImage(for: tab)) - .renderingMode(.original) - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(.horizontal, 8) - .padding(.vertical, 6) + if let image = tabImage(for: tab) { + Image(uiImage: image) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal, 8) + .padding(.vertical, 6) + } else { + Text(tab.title ?? "") + .font(.system(size: 14, weight: state.currentTab == tab ? .semibold : .regular)) + .foregroundColor(.black) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } } .buttonStyle(.plain) .background(Color(white: 0.768627451)) @@ -52,14 +59,16 @@ public struct MathKeyboardRootView: View { } } - private func tabImage(for tab: KeyboardTab) -> UIImage { - let names = tab.imageNames + private func tabImage(for tab: KeyboardTab) -> UIImage? { + guard let names = tab.imageNames else { + return nil + } let name = state.currentTab == tab ? names.selected : names.normal return UIImage( named: name, in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), compatibleWith: nil - ) ?? UIImage() + ) } } diff --git a/mathKeyboardSwiftUI/NumbersKeyboardView.swift b/mathKeyboardSwiftUI/NumbersKeyboardView.swift new file mode 100644 index 0000000..e315875 --- /dev/null +++ b/mathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -0,0 +1,570 @@ +#if os(iOS) + +import MathEditor +import MathKeyboard +import SwiftUI +import CoreText +import Foundation +import UIKit + +protocol KeyboardConfigurable: AnyObject { + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) + func setNumbersState(_ enabled: Bool) + func setOperatorState(_ enabled: Bool) + func setVariablesState(_ enabled: Bool) + func setFractionState(_ enabled: Bool) + func setEqualsState(_ enabled: Bool) + func setExponentState(_ highlighted: Bool) + func setSquareRootState(_ highlighted: Bool) + func setRadicalState(_ highlighted: Bool) +} + +extension MTKeyboard: KeyboardConfigurable { + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + self.textView = textView + } +} + +final class NumbersKeyboardModel: ObservableObject { + @Published var numbersAllowed = true + @Published var operatorsAllowed = true + @Published var variablesAllowed = true + @Published var fractionsAllowed = true + @Published var equalsAllowed = true + @Published var exponentHighlighted = false +} + +private enum KeyboardFontRegistry { + static let variableFontName: String = { + guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { + return "HelveticaNeue" + } + guard + let fontURL = bundle.url(forResource: "lmroman10-bolditalic", withExtension: "otf"), + let provider = CGDataProvider(url: fontURL as CFURL), + let font = CGFont(provider) + else { + return "HelveticaNeue" + } + + let postScriptName = font.postScriptName as String? ?? "HelveticaNeue" + var error: Unmanaged? + CTFontManagerRegisterGraphicsFont(font, &error) + return postScriptName + }() +} + +final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { + private let model = NumbersKeyboardModel() + private weak var editingTarget: (any UIView & UIKeyInput)? + private lazy var hostingController = UIHostingController( + rootView: NumbersKeyboardView( + model: model, + onInsertText: { [weak self] text in self?.insert(text) }, + onBackspace: { [weak self] in self?.backspace() }, + onDismiss: { [weak self] in self?.dismissKeyboard() } + ) + ) + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + editingTarget = textView + } + + func setNumbersState(_ enabled: Bool) { + updateModel(\.numbersAllowed, to: enabled) + } + + func setOperatorState(_ enabled: Bool) { + updateModel(\.operatorsAllowed, to: enabled) + } + + func setVariablesState(_ enabled: Bool) { + updateModel(\.variablesAllowed, to: enabled) + } + + func setFractionState(_ enabled: Bool) { + updateModel(\.fractionsAllowed, to: enabled) + } + + func setEqualsState(_ enabled: Bool) { + updateModel(\.equalsAllowed, to: enabled) + } + + func setExponentState(_ highlighted: Bool) { + updateModel(\.exponentHighlighted, to: highlighted) + } + + func setSquareRootState(_ highlighted: Bool) {} + + func setRadicalState(_ highlighted: Bool) {} + + var enableInputClicksWhenVisible: Bool { true } + + private func commonInit() { + backgroundColor = .white + + let hostedView = hostingController.view! + if #available(iOS 16.4, *) { + hostingController.safeAreaRegions = [] + } + hostedView.backgroundColor = .clear + hostedView.translatesAutoresizingMaskIntoConstraints = false + addSubview(hostedView) + + NSLayoutConstraint.activate([ + hostedView.topAnchor.constraint(equalTo: topAnchor), + hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), + hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), + hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + private func insert(_ text: String) { + playClickForCustomKeyTap() + editingTarget?.insertText(text) + } + + private func backspace() { + playClickForCustomKeyTap() + editingTarget?.deleteBackward() + } + + private func dismissKeyboard() { + playClickForCustomKeyTap() + editingTarget?.resignFirstResponder() + } + + private func playClickForCustomKeyTap() { + UIDevice.current.playInputClick() + } + + private func updateModel( + _ keyPath: ReferenceWritableKeyPath, + to value: Bool + ) { + guard model[keyPath: keyPath] != value else { return } + + DispatchQueue.main.async { [weak self] in + guard let self, self.model[keyPath: keyPath] != value else { return } + self.model[keyPath: keyPath] = value + } + } +} + +struct NumbersKeyboardView: View { + @ObservedObject var model: NumbersKeyboardModel + let onInsertText: (String) -> Void + let onBackspace: () -> Void + let onDismiss: () -> Void + + private let canvasSize = CGSize(width: 320, height: 180) + + var body: some View { + GeometryReader { proxy in + stretchedKeyboardCanvas(in: proxy.size) + .frame(width: proxy.size.width, height: proxy.size.height, alignment: .topLeading) + .clipped() + } + } + + private func stretchedKeyboardCanvas(in size: CGSize) -> some View { + ZStack(alignment: .topLeading) { + assetImage("Numbers Keyboard") + .resizable() + .frame(width: size.width, height: size.height) + + if model.exponentHighlighted { + stretchedOverlayImage("blue-button-highlighted", frame: keyboardFrame(column: .feature, row: .bottom, width: 50), in: size) + } + + if !model.equalsAllowed { + stretchedOverlayImage("num-button-disabled", frame: keyboardFrame(column: .numbersRight, row: .bottom), in: size) + } + + ForEach(keys) { key in + stretchedTappableKey(key, in: size) + } + + ForEach(labels) { label in + stretchedKeyboardLabel(label, in: size) + } + } + } + + private var keys: [KeyboardKey] { + [ + .text("x", action: { onInsertText("x") }, frame: keyboardFrame(column: .feature, row: .top), enabled: model.variablesAllowed), + .text("y", action: { onInsertText("y") }, frame: keyboardFrame(column: .feature, row: .upperMiddle), enabled: model.variablesAllowed), + .custom(action: { onInsertText(MTSymbolFractionSlash) }, frame: keyboardFrame(column: .feature, row: .lowerMiddle), enabled: model.fractionsAllowed, pressedAsset: "Keyboard-marine-pressed", accessibilityLabel: "Fraction"), + .custom(action: { onInsertText("^") }, frame: keyboardFrame(column: .feature, row: .bottom), enabled: true, pressedAsset: "Keyboard-marine-pressed", accessibilityLabel: "Exponent"), + + .text("7", action: { onInsertText("7") }, frame: keyboardFrame(column: .numbersLeft, row: .top), enabled: model.numbersAllowed), + .text("8", action: { onInsertText("8") }, frame: keyboardFrame(column: .numbersMiddle, row: .top), enabled: model.numbersAllowed), + .text("9", action: { onInsertText("9") }, frame: keyboardFrame(column: .numbersRight, row: .top), enabled: model.numbersAllowed), + .text("4", action: { onInsertText("4") }, frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), enabled: model.numbersAllowed), + .text("5", action: { onInsertText("5") }, frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), enabled: model.numbersAllowed), + .text("6", action: { onInsertText("6") }, frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), enabled: model.numbersAllowed), + .text("1", action: { onInsertText("1") }, frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), enabled: model.numbersAllowed), + .text("2", action: { onInsertText("2") }, frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), enabled: model.numbersAllowed), + .text("3", action: { onInsertText("3") }, frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), enabled: model.numbersAllowed), + .text("0", action: { onInsertText("0") }, frame: keyboardFrame(column: .numbersLeft, row: .bottom), enabled: model.numbersAllowed), + .text(".", action: { onInsertText(".") }, frame: keyboardFrame(column: .numbersMiddle, row: .bottom), enabled: model.numbersAllowed), + .text("=", action: { onInsertText("=") }, frame: keyboardFrame(column: .numbersRight, row: .bottom), enabled: model.equalsAllowed), + .text("÷", action: { onInsertText("÷") }, frame: keyboardFrame(column: .operators, row: .top), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text("×", action: { onInsertText("×") }, frame: keyboardFrame(column: .operators, row: .upperMiddle), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text("-", action: { onInsertText("-") }, frame: keyboardFrame(column: .operators, row: .lowerMiddle), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text("+", action: { onInsertText("+") }, frame: keyboardFrame(column: .operators, row: .bottom), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .custom(action: onBackspace, frame: keyboardFrame(column: .utility, row: .top), enabled: true, pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Backspace"), + .custom(action: { onInsertText("\n") }, frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), enabled: true, pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Enter"), + .custom(action: onDismiss, frame: keyboardFrame(column: .utility, row: .bottom), enabled: true, pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Dismiss keyboard"), + ] + } + + private var labels: [KeyboardLabel] { + [ + .text("x", frame: keyboardFrame(column: .feature, row: .top), color: .white, fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), + .text("y", frame: keyboardFrame(column: .feature, row: .upperMiddle), color: .white, fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), + .image("Fraction", frame: keyboardFrame(column: .feature, row: .lowerMiddle), imageInsets: UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 0)), + .image("Exponent", frame: keyboardFrame(column: .feature, row: .bottom), imageInsets: .zero), + + .text("7", frame: keyboardFrame(column: .numbersLeft, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("8", frame: keyboardFrame(column: .numbersMiddle, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("9", frame: keyboardFrame(column: .numbersRight, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("4", frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("5", frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("6", frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("1", frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("2", frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("3", frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("0", frame: keyboardFrame(column: .numbersLeft, row: .bottom), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text(".", frame: keyboardFrame(column: .numbersMiddle, row: .bottom), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), + .text("=", frame: keyboardFrame(column: .numbersRight, row: .bottom), color: model.equalsAllowed ? .black : Color(white: 0.67), fontName: "HelveticaNeue-Thin", fontSize: 25, bottomInset: 2), + .text("÷", frame: keyboardFrame(column: .operators, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), + .text("×", frame: keyboardFrame(column: .operators, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 7), + .text("-", frame: keyboardFrame(column: .operators, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 25, bottomInset: 9), + .text("+", frame: keyboardFrame(column: .operators, row: .bottom), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), + .image("Backspace", frame: keyboardFrame(column: .utility, row: .top), imageInsets: .zero), + .text("Enter", frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), color: .white, fontName: "HelveticaNeue-Light", fontSize: 20), + .image("Keyboard Down", frame: keyboardFrame(column: .utility, row: .bottom), imageInsets: UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0), imageOffset: CGSize(width: 0, height: -1.5)), + ] + } + + private func keyboardFrame( + column: KeyboardColumn, + row: KeyboardRow, + rowSpan: CGFloat = 1, + width: CGFloat? = nil + ) -> CGRect { + CGRect( + x: column.x, + y: row.y, + width: width ?? column.width, + height: row.height * rowSpan + ) + } + + private func stretchedTappableKey(_ key: KeyboardKey, in size: CGSize) -> some View { + StretchedKeyboardButton( + key: key, + canvasSize: canvasSize, + containerSize: size, + pressedOverlay: { + if let asset = key.pressedAsset { + assetImage(asset) + .resizable() + } else { + Color.clear + } + } + ) + } + + private func stretchedOverlayImage(_ name: String, frame: CGRect, in size: CGSize) -> some View { + assetImage(name) + .resizable() + .frame(width: scaledWidth(frame.width, in: size), height: scaledHeight(frame.height, in: size)) + .position( + x: scaled(frame.midX, from: canvasSize.width, to: size.width), + y: scaled(frame.midY, from: canvasSize.height, to: size.height) + ) + .allowsHitTesting(false) + } + + @ViewBuilder + private func stretchedKeyboardLabel(_ label: KeyboardLabel, in size: CGSize) -> some View { + switch label.content { + case .text(let style): + Text(style.value) + .font(.custom(style.fontName, size: style.fontSize)) + .foregroundColor(style.color) + .frame(width: scaledWidth(label.frame.width, in: size), height: scaledHeight(label.frame.height, in: size)) + .position( + x: scaled(label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), + y: scaled(label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) - (style.bottomInset / 2) + ) + .allowsHitTesting(false) + + case .image(let style): + if let image = assetUIImage(style.name) { + let contentWidth = scaled(label.frame.width - style.insets.left - style.insets.right, from: canvasSize.width, to: size.width) + let contentHeight = scaled(label.frame.height - style.insets.top - style.insets.bottom, from: canvasSize.height, to: size.height) + let intrinsicWidth = image.size.width + let intrinsicHeight = image.size.height + let fitScale = min(contentWidth / intrinsicWidth, contentHeight / intrinsicHeight, 1) + let renderedWidth = intrinsicWidth * fitScale + let renderedHeight = intrinsicHeight * fitScale + + Image(uiImage: image) + .renderingMode(.original) + .resizable() + .frame(width: renderedWidth, height: renderedHeight) + .position( + x: scaled(label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), + y: scaled(label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) + ) + .allowsHitTesting(false) + } + } + } + + private func assetImage(_ name: String) -> Image { + if let image = assetUIImage(name) { + return Image(uiImage: image) + } + return Image(systemName: "questionmark.square.dashed") + } + + private func assetUIImage(_ name: String) -> UIImage? { + UIImage( + named: name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) + } + + private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { + (value / source) * target + } + + private func scaledWidth(_ value: CGFloat, in size: CGSize) -> CGFloat { + scaled(value, from: canvasSize.width, to: size.width) + } + + private func scaledHeight(_ value: CGFloat, in size: CGSize) -> CGFloat { + scaled(value, from: canvasSize.height, to: size.height) + } +} + +private enum KeyboardColumn { + case feature + case numbersLeft + case numbersMiddle + case numbersRight + case operators + case utility + + var x: CGFloat { + switch self { + case .feature: 0 + case .numbersLeft: 50 + case .numbersMiddle: 99.6667 + case .numbersRight: 149.3333 + case .operators: 199 + case .utility: 248 + } + } + + var width: CGFloat { + switch self { + case .feature, .numbersMiddle, .numbersRight: 49.6667 + case .numbersLeft, .operators: 49 + case .utility: 72 + } + } +} + +private enum KeyboardRow { + case top + case upperMiddle + case lowerMiddle + case bottom + + var y: CGFloat { + switch self { + case .top: 0 + case .upperMiddle: 45 + case .lowerMiddle: 90 + case .bottom: 135 + } + } + + var height: CGFloat { 45 } +} + +private struct KeyboardKey: Identifiable { + let id = UUID() + let action: () -> Void + let frame: CGRect + let enabled: Bool + let pressedAsset: String? + let accessibilityLabel: String + + static func text( + _ label: String, + action: @escaping () -> Void, + frame: CGRect, + enabled: Bool, + pressedAsset: String = "Keyboard-grey-pressed" + ) -> KeyboardKey { + KeyboardKey( + action: action, + frame: frame, + enabled: enabled, + pressedAsset: pressedAsset, + accessibilityLabel: label + ) + } + + static func custom( + action: @escaping () -> Void, + frame: CGRect, + enabled: Bool, + pressedAsset: String, + accessibilityLabel: String + ) -> KeyboardKey { + KeyboardKey( + action: action, + frame: frame, + enabled: enabled, + pressedAsset: pressedAsset, + accessibilityLabel: accessibilityLabel + ) + } +} + +private struct KeyboardLabel: Identifiable { + enum Content { + case text(TextStyle) + case image(ImageStyle) + } + + struct TextStyle { + let value: String + let color: Color + let fontName: String + let fontSize: CGFloat + let bottomInset: CGFloat + let offset: CGSize + } + + struct ImageStyle { + let name: String + let insets: UIEdgeInsets + let offset: CGSize + } + + let id = UUID() + let content: Content + let frame: CGRect + + static func text( + _ value: String, + frame: CGRect, + color: Color, + fontName: String, + fontSize: CGFloat, + bottomInset: CGFloat = 0, + offset: CGSize = .zero + ) -> KeyboardLabel { + KeyboardLabel( + content: .text( + TextStyle( + value: value, + color: color, + fontName: fontName, + fontSize: fontSize, + bottomInset: bottomInset, + offset: offset + ) + ), + frame: frame + ) + } + + static func image( + _ name: String, + frame: CGRect, + imageInsets: UIEdgeInsets, + imageOffset: CGSize = .zero + ) -> KeyboardLabel { + KeyboardLabel( + content: .image( + ImageStyle( + name: name, + insets: imageInsets, + offset: imageOffset + ) + ), + frame: frame + ) + } +} + +private struct StretchedKeyboardButton: View { + let key: KeyboardKey + let canvasSize: CGSize + let containerSize: CGSize + @ViewBuilder let pressedOverlay: () -> PressedOverlay + + var body: some View { + Button(action: key.action) { + Rectangle() + .fill(Color.white.opacity(0.001)) + .contentShape(Rectangle()) + } + .buttonStyle( + KeyboardTapTargetStyle( + pressedOverlay: AnyView(pressedOverlay()) + ) + ) + .disabled(!key.enabled) + .frame( + width: scaled(key.frame.width, from: canvasSize.width, to: containerSize.width), + height: scaled(key.frame.height, from: canvasSize.height, to: containerSize.height) + ) + .position( + x: scaled(key.frame.midX, from: canvasSize.width, to: containerSize.width), + y: scaled(key.frame.midY, from: canvasSize.height, to: containerSize.height) + ) + .accessibility(label: Text(key.accessibilityLabel)) + } + + private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { + (value / source) * target + } +} + +private struct KeyboardTapTargetStyle: ButtonStyle { + let pressedOverlay: AnyView + + func makeBody(configuration: Configuration) -> some View { + ZStack { + configuration.label + if configuration.isPressed { + pressedOverlay + } + } + } +} + +#endif From 667b8ba1935664eaec1ba7a0f52cb12b5b90f9c6 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 09:18:10 +0000 Subject: [PATCH 054/133] New SwiftUI package --- .../project.pbxproj | 11 +- MathKeyboardSwiftUI/Package.resolved | 14 + MathKeyboardSwiftUI/Package.swift | 28 + .../KeyboardContainerView.swift | 94 +++ .../MathKeyboardSwiftUI}/KeyboardState.swift | 0 .../MathKeyboardSwiftUI}/KeyboardTab.swift | 0 .../MTMathKeyboardSwiftUIRootView.swift | 0 .../MathKeyboardRootView.swift | 75 ++ .../NumbersKeyboardView.swift | 693 ++++++++++++++++++ Package.swift | 8 - .../KeyboardContainerView.swift | 94 --- .../MathKeyboardRootView.swift | 75 -- mathKeyboardSwiftUI/NumbersKeyboardView.swift | 570 -------------- 13 files changed, 912 insertions(+), 750 deletions(-) create mode 100644 MathKeyboardSwiftUI/Package.resolved create mode 100644 MathKeyboardSwiftUI/Package.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift rename {mathKeyboardSwiftUI => MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI}/KeyboardState.swift (100%) rename {mathKeyboardSwiftUI => MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI}/KeyboardTab.swift (100%) rename {mathKeyboardSwiftUI => MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI}/MTMathKeyboardSwiftUIRootView.swift (100%) create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift delete mode 100644 mathKeyboardSwiftUI/KeyboardContainerView.swift delete mode 100644 mathKeyboardSwiftUI/MathKeyboardRootView.swift delete mode 100644 mathKeyboardSwiftUI/NumbersKeyboardView.swift diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj index fcb8b72..d9ce350 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj @@ -119,6 +119,7 @@ minimizedProjectReferenceProxies = 1; packageReferences = ( 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */, + C1C5F2D42FF0000000000001 /* XCLocalSwiftPackageReference "../../MathEditor/MathKeyboardSwiftUI" */, ); preferredProjectObjectVersion = 77; productRefGroup = 862D28E82E2D51E100F9B6FE /* Products */; @@ -289,7 +290,7 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14; + IPHONEOS_DEPLOYMENT_TARGET = 16; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 11; @@ -328,7 +329,7 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14; + IPHONEOS_DEPLOYMENT_TARGET = 16; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 11; @@ -373,6 +374,10 @@ isa = XCLocalSwiftPackageReference; relativePath = ../../MathEditor; }; + C1C5F2D42FF0000000000001 /* XCLocalSwiftPackageReference "../../MathEditor/MathKeyboardSwiftUI" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../MathEditor/MathKeyboardSwiftUI; + }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -391,7 +396,7 @@ }; C1DAAE002F70231100E4E983 /* MathKeyboardSwiftUI */ = { isa = XCSwiftPackageProductDependency; - package = 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */; + package = C1C5F2D42FF0000000000001 /* XCLocalSwiftPackageReference "../../MathEditor/MathKeyboardSwiftUI" */; productName = MathKeyboardSwiftUI; }; /* End XCSwiftPackageProductDependency section */ diff --git a/MathKeyboardSwiftUI/Package.resolved b/MathKeyboardSwiftUI/Package.resolved new file mode 100644 index 0000000..24b9309 --- /dev/null +++ b/MathKeyboardSwiftUI/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "iosmath", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maitbayev/iosMath.git", + "state" : { + "branch" : "master", + "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836" + } + } + ], + "version" : 2 +} diff --git a/MathKeyboardSwiftUI/Package.swift b/MathKeyboardSwiftUI/Package.swift new file mode 100644 index 0000000..94e5dc7 --- /dev/null +++ b/MathKeyboardSwiftUI/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "MathKeyboardSwiftUI", + defaultLocalization: "en", + platforms: [.iOS(.v16), .macOS(.v11)], + products: [ + .library( + name: "MathKeyboardSwiftUI", + targets: ["MathKeyboardSwiftUI"] + ) + ], + dependencies: [ + .package(path: "..") + ], + targets: [ + .target( + name: "MathKeyboardSwiftUI", + dependencies: [ + .product(name: "MathKeyboard", package: "MathEditor"), + .product(name: "MathEditor", package: "MathEditor"), + ], + path: "Sources/MathKeyboardSwiftUI" + ) + ] +) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift new file mode 100644 index 0000000..5190473 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -0,0 +1,94 @@ +// +// KeyboardContainerView.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 22/03/2026. +// + +#if os(iOS) + + import MathEditor + import MathKeyboard + import SwiftUI + import UIKit + + struct KeyboardContainerView: UIViewRepresentable { + let state: KeyboardState + weak var textInput: (any UIView & UIKeyInput)? + + func makeUIView(context: Context) -> KeyboardContainerUIView { + let view = KeyboardContainerUIView() + view.sync( + state: state, + editingTarget: textInput + ) + return view + } + + func updateUIView(_ uiView: KeyboardContainerUIView, context: Context) { + uiView.sync( + state: state, + editingTarget: textInput + ) + } + } + + final class KeyboardContainerUIView: UIView { + private weak var currentKeyboard: UIView? + private lazy var keyboards: [KeyboardTab: (UIView & KeyboardConfigurable)] = [ + .numbers: makeNumbersKeyboard(), + .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), + .operations: makeKeyboard(named: KeyboardTab.operations.nibName), + .functions: makeKeyboard(named: KeyboardTab.functions.nibName), + .letters: makeKeyboard(named: KeyboardTab.letters.nibName), + ] + + fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { + for keyboard in keyboards.values { + keyboard.setEditingTarget(editingTarget) + keyboard.setEqualsState(state.equalsAllowed) + keyboard.setFractionState(state.fractionsAllowed) + keyboard.setVariablesState(state.variablesAllowed) + keyboard.setNumbersState(state.numbersAllowed) + keyboard.setOperatorState(state.operatorsAllowed) + keyboard.setExponentState(state.exponentHighlighted) + keyboard.setSquareRootState(state.squareRootHighlighted) + keyboard.setRadicalState(state.radicalHighlighted) + } + + display(keyboard: keyboards[state.currentTab]!) + } + + fileprivate func display(keyboard: UIView) { + guard currentKeyboard !== keyboard else { return } + + currentKeyboard?.removeFromSuperview() + currentKeyboard = keyboard + addSubview(keyboard) + + NSLayoutConstraint.activate([ + keyboard.topAnchor.constraint(equalTo: topAnchor), + keyboard.leadingAnchor.constraint(equalTo: leadingAnchor), + keyboard.trailingAnchor.constraint(equalTo: trailingAnchor), + keyboard.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { + let keyboard = NumbersKeyboardHostView() + keyboard.translatesAutoresizingMaskIntoConstraints = false + return keyboard + } + + private func makeKeyboard(named nibName: String) -> UIView & KeyboardConfigurable { + let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() + let keyboard = UINib(nibName: nibName, bundle: bundle) + .instantiate(withOwner: nil, options: nil) + .compactMap { $0 as? MTKeyboard } + .first! + keyboard.translatesAutoresizingMaskIntoConstraints = false + return keyboard + } + } + +#endif // os(iOS) diff --git a/mathKeyboardSwiftUI/KeyboardState.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift similarity index 100% rename from mathKeyboardSwiftUI/KeyboardState.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift diff --git a/mathKeyboardSwiftUI/KeyboardTab.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift similarity index 100% rename from mathKeyboardSwiftUI/KeyboardTab.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift diff --git a/mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift similarity index 100% rename from mathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift new file mode 100644 index 0000000..e657f84 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -0,0 +1,75 @@ +// +// MathKeyboardRootView.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 22/03/2026. +// + +import MathKeyboard +import SwiftUI + +#if os(iOS) + + public struct MathKeyboardRootView: View { + let state: KeyboardState + weak var textInput: (any UIView & UIKeyInput)? + let onTabSelected: (KeyboardTab) -> Void + + public var body: some View { + GeometryReader { proxy in + let totalHeight = proxy.size.height + let tabHeight = totalHeight / 5.0 + let keyboardHeight = totalHeight - tabHeight + + VStack(spacing: 0) { + HStack(spacing: 0) { + ForEach(KeyboardTab.allCases) { tab in + Button { + onTabSelected(tab) + } label: { + if let image = tabImage(for: tab) { + Image(uiImage: image) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal, 8) + .padding(.vertical, 6) + } else { + Text(tab.title ?? "") + .font(.system(size: 14, weight: state.currentTab == tab ? .semibold : .regular)) + .foregroundColor(.black) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .buttonStyle(.plain) + .background(Color(white: 0.768627451)) + } + } + .frame(height: tabHeight) + + KeyboardContainerView( + state: state, + textInput: textInput + ) + .frame(height: keyboardHeight) + } + .background(Color.white) + .ignoresSafeArea() + } + } + + private func tabImage(for tab: KeyboardTab) -> UIImage? { + guard let names = tab.imageNames else { + return nil + } + let name = state.currentTab == tab ? names.selected : names.normal + return UIImage( + named: name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) + } + } + +#endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift new file mode 100644 index 0000000..bc3b4fc --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -0,0 +1,693 @@ +#if os(iOS) + + import MathEditor + import MathKeyboard + import SwiftUI + import CoreText + import Foundation + import UIKit + + protocol KeyboardConfigurable: AnyObject { + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) + func setNumbersState(_ enabled: Bool) + func setOperatorState(_ enabled: Bool) + func setVariablesState(_ enabled: Bool) + func setFractionState(_ enabled: Bool) + func setEqualsState(_ enabled: Bool) + func setExponentState(_ highlighted: Bool) + func setSquareRootState(_ highlighted: Bool) + func setRadicalState(_ highlighted: Bool) + } + + extension MTKeyboard: KeyboardConfigurable { + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + self.textView = textView + } + } + + final class NumbersKeyboardModel: ObservableObject { + @Published var numbersAllowed = true + @Published var operatorsAllowed = true + @Published var variablesAllowed = true + @Published var fractionsAllowed = true + @Published var equalsAllowed = true + @Published var exponentHighlighted = false + } + + private enum KeyboardFontRegistry { + static let variableFontName: String = { + guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { + return "HelveticaNeue" + } + guard + let fontURL = bundle.url(forResource: "lmroman10-bolditalic", withExtension: "otf"), + let provider = CGDataProvider(url: fontURL as CFURL), + let font = CGFont(provider) + else { + return "HelveticaNeue" + } + + let postScriptName = font.postScriptName as String? ?? "HelveticaNeue" + var error: Unmanaged? + CTFontManagerRegisterGraphicsFont(font, &error) + return postScriptName + }() + } + + final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { + private let model = NumbersKeyboardModel() + private weak var editingTarget: (any UIView & UIKeyInput)? + private lazy var hostingController = UIHostingController( + rootView: NumbersKeyboardView( + model: model, + onInsertText: { [weak self] text in self?.insert(text) }, + onBackspace: { [weak self] in self?.backspace() }, + onDismiss: { [weak self] in self?.dismissKeyboard() } + ) + ) + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + editingTarget = textView + } + + func setNumbersState(_ enabled: Bool) { + updateModel(\.numbersAllowed, to: enabled) + } + + func setOperatorState(_ enabled: Bool) { + updateModel(\.operatorsAllowed, to: enabled) + } + + func setVariablesState(_ enabled: Bool) { + updateModel(\.variablesAllowed, to: enabled) + } + + func setFractionState(_ enabled: Bool) { + updateModel(\.fractionsAllowed, to: enabled) + } + + func setEqualsState(_ enabled: Bool) { + updateModel(\.equalsAllowed, to: enabled) + } + + func setExponentState(_ highlighted: Bool) { + updateModel(\.exponentHighlighted, to: highlighted) + } + + func setSquareRootState(_ highlighted: Bool) {} + + func setRadicalState(_ highlighted: Bool) {} + + var enableInputClicksWhenVisible: Bool { true } + + private func commonInit() { + backgroundColor = .white + + let hostedView = hostingController.view! + if #available(iOS 16.4, *) { + hostingController.safeAreaRegions = [] + } + hostedView.backgroundColor = .clear + hostedView.translatesAutoresizingMaskIntoConstraints = false + addSubview(hostedView) + + NSLayoutConstraint.activate([ + hostedView.topAnchor.constraint(equalTo: topAnchor), + hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), + hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), + hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + private func insert(_ text: String) { + playClickForCustomKeyTap() + editingTarget?.insertText(text) + } + + private func backspace() { + playClickForCustomKeyTap() + editingTarget?.deleteBackward() + } + + private func dismissKeyboard() { + playClickForCustomKeyTap() + editingTarget?.resignFirstResponder() + } + + private func playClickForCustomKeyTap() { + UIDevice.current.playInputClick() + } + + private func updateModel( + _ keyPath: ReferenceWritableKeyPath, + to value: Bool + ) { + guard model[keyPath: keyPath] != value else { return } + + DispatchQueue.main.async { [weak self] in + guard let self, self.model[keyPath: keyPath] != value else { return } + self.model[keyPath: keyPath] = value + } + } + } + + struct NumbersKeyboardView: View { + @ObservedObject var model: NumbersKeyboardModel + let onInsertText: (String) -> Void + let onBackspace: () -> Void + let onDismiss: () -> Void + + private let canvasSize = CGSize(width: 320, height: 180) + + var body: some View { + GeometryReader { proxy in + stretchedKeyboardCanvas(in: proxy.size) + .frame(width: proxy.size.width, height: proxy.size.height, alignment: .topLeading) + .clipped() + } + } + + private func stretchedKeyboardCanvas(in size: CGSize) -> some View { + ZStack(alignment: .topLeading) { + assetImage("Numbers Keyboard") + .resizable() + .frame(width: size.width, height: size.height) + + if model.exponentHighlighted { + stretchedOverlayImage( + "blue-button-highlighted", + frame: keyboardFrame(column: .feature, row: .bottom, width: 50), in: size) + } + + if !model.equalsAllowed { + stretchedOverlayImage( + "num-button-disabled", frame: keyboardFrame(column: .numbersRight, row: .bottom), + in: size) + } + + ForEach(keys) { key in + stretchedTappableKey(key, in: size) + } + + ForEach(labels) { label in + stretchedKeyboardLabel(label, in: size) + } + } + } + + private var keys: [KeyboardKey] { + [ + .text( + "x", action: { onInsertText("x") }, frame: keyboardFrame(column: .feature, row: .top), + enabled: model.variablesAllowed), + .text( + "y", action: { onInsertText("y") }, + frame: keyboardFrame(column: .feature, row: .upperMiddle), enabled: model.variablesAllowed + ), + .custom( + action: { onInsertText(MTSymbolFractionSlash) }, + frame: keyboardFrame(column: .feature, row: .lowerMiddle), + enabled: model.fractionsAllowed, pressedAsset: "Keyboard-marine-pressed", + accessibilityLabel: "Fraction"), + .custom( + action: { onInsertText("^") }, frame: keyboardFrame(column: .feature, row: .bottom), + enabled: true, pressedAsset: "Keyboard-marine-pressed", accessibilityLabel: "Exponent"), + + .text( + "7", action: { onInsertText("7") }, frame: keyboardFrame(column: .numbersLeft, row: .top), + enabled: model.numbersAllowed), + .text( + "8", action: { onInsertText("8") }, + frame: keyboardFrame(column: .numbersMiddle, row: .top), enabled: model.numbersAllowed), + .text( + "9", action: { onInsertText("9") }, + frame: keyboardFrame(column: .numbersRight, row: .top), enabled: model.numbersAllowed), + .text( + "4", action: { onInsertText("4") }, + frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), + enabled: model.numbersAllowed), + .text( + "5", action: { onInsertText("5") }, + frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), + enabled: model.numbersAllowed), + .text( + "6", action: { onInsertText("6") }, + frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), + enabled: model.numbersAllowed), + .text( + "1", action: { onInsertText("1") }, + frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), + enabled: model.numbersAllowed), + .text( + "2", action: { onInsertText("2") }, + frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), + enabled: model.numbersAllowed), + .text( + "3", action: { onInsertText("3") }, + frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), + enabled: model.numbersAllowed), + .text( + "0", action: { onInsertText("0") }, + frame: keyboardFrame(column: .numbersLeft, row: .bottom), enabled: model.numbersAllowed), + .text( + ".", action: { onInsertText(".") }, + frame: keyboardFrame(column: .numbersMiddle, row: .bottom), enabled: model.numbersAllowed), + .text( + "=", action: { onInsertText("=") }, + frame: keyboardFrame(column: .numbersRight, row: .bottom), enabled: model.equalsAllowed), + .text( + "÷", action: { onInsertText("÷") }, frame: keyboardFrame(column: .operators, row: .top), + enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + "×", action: { onInsertText("×") }, + frame: keyboardFrame(column: .operators, row: .upperMiddle), + enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + "-", action: { onInsertText("-") }, + frame: keyboardFrame(column: .operators, row: .lowerMiddle), + enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + "+", action: { onInsertText("+") }, + frame: keyboardFrame(column: .operators, row: .bottom), enabled: model.operatorsAllowed, + pressedAsset: "Keyboard-orange-pressed"), + .custom( + action: onBackspace, frame: keyboardFrame(column: .utility, row: .top), enabled: true, + pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Backspace"), + .custom( + action: { onInsertText("\n") }, + frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), enabled: true, + pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Enter"), + .custom( + action: onDismiss, frame: keyboardFrame(column: .utility, row: .bottom), enabled: true, + pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Dismiss keyboard"), + ] + } + + private var labels: [KeyboardLabel] { + [ + .text( + "x", frame: keyboardFrame(column: .feature, row: .top), color: .white, + fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), + .text( + "y", frame: keyboardFrame(column: .feature, row: .upperMiddle), color: .white, + fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), + .image( + "Fraction", frame: keyboardFrame(column: .feature, row: .lowerMiddle), + imageInsets: UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 0)), + .image( + "Exponent", frame: keyboardFrame(column: .feature, row: .bottom), imageInsets: .zero), + + .text( + "7", frame: keyboardFrame(column: .numbersLeft, row: .top), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "8", frame: keyboardFrame(column: .numbersMiddle, row: .top), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "9", frame: keyboardFrame(column: .numbersRight, row: .top), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "4", frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "5", frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "6", frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "1", frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "2", frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "3", frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "0", frame: keyboardFrame(column: .numbersLeft, row: .bottom), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + ".", frame: keyboardFrame(column: .numbersMiddle, row: .bottom), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20), + .text( + "=", frame: keyboardFrame(column: .numbersRight, row: .bottom), + color: model.equalsAllowed ? .black : Color(white: 0.67), fontName: "HelveticaNeue-Thin", + fontSize: 25, bottomInset: 2), + .text( + "÷", frame: keyboardFrame(column: .operators, row: .top), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), + .text( + "×", frame: keyboardFrame(column: .operators, row: .upperMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 7), + .text( + "-", frame: keyboardFrame(column: .operators, row: .lowerMiddle), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 25, bottomInset: 9), + .text( + "+", frame: keyboardFrame(column: .operators, row: .bottom), color: .black, + fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), + .image("Backspace", frame: keyboardFrame(column: .utility, row: .top), imageInsets: .zero), + .text( + "Enter", frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), + color: .white, fontName: "HelveticaNeue-Light", fontSize: 20), + .image( + "Keyboard Down", frame: keyboardFrame(column: .utility, row: .bottom), + imageInsets: UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0), + imageOffset: CGSize(width: 0, height: -1.5)), + ] + } + + private func keyboardFrame( + column: KeyboardColumn, + row: KeyboardRow, + rowSpan: CGFloat = 1, + width: CGFloat? = nil + ) -> CGRect { + CGRect( + x: column.x, + y: row.y, + width: width ?? column.width, + height: row.height * rowSpan + ) + } + + private func stretchedTappableKey(_ key: KeyboardKey, in size: CGSize) -> some View { + StretchedKeyboardButton( + key: key, + canvasSize: canvasSize, + containerSize: size, + pressedOverlay: { + if let asset = key.pressedAsset { + assetImage(asset) + .resizable() + } else { + Color.clear + } + } + ) + } + + private func stretchedOverlayImage(_ name: String, frame: CGRect, in size: CGSize) -> some View + { + assetImage(name) + .resizable() + .frame( + width: scaledWidth(frame.width, in: size), height: scaledHeight(frame.height, in: size) + ) + .position( + x: scaled(frame.midX, from: canvasSize.width, to: size.width), + y: scaled(frame.midY, from: canvasSize.height, to: size.height) + ) + .allowsHitTesting(false) + } + + @ViewBuilder + private func stretchedKeyboardLabel(_ label: KeyboardLabel, in size: CGSize) -> some View { + switch label.content { + case .text(let style): + Text(style.value) + .font(.custom(style.fontName, size: style.fontSize)) + .foregroundColor(style.color) + .frame( + width: scaledWidth(label.frame.width, in: size), + height: scaledHeight(label.frame.height, in: size) + ) + .position( + x: scaled( + label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), + y: scaled( + label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) + - (style.bottomInset / 2) + ) + .allowsHitTesting(false) + + case .image(let style): + if let image = assetUIImage(style.name) { + let contentWidth = scaled( + label.frame.width - style.insets.left - style.insets.right, from: canvasSize.width, + to: size.width) + let contentHeight = scaled( + label.frame.height - style.insets.top - style.insets.bottom, from: canvasSize.height, + to: size.height) + let intrinsicWidth = image.size.width + let intrinsicHeight = image.size.height + let fitScale = min(contentWidth / intrinsicWidth, contentHeight / intrinsicHeight, 1) + let renderedWidth = intrinsicWidth * fitScale + let renderedHeight = intrinsicHeight * fitScale + + Image(uiImage: image) + .renderingMode(.original) + .resizable() + .frame(width: renderedWidth, height: renderedHeight) + .position( + x: scaled( + label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), + y: scaled( + label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) + ) + .allowsHitTesting(false) + } + } + } + + private func assetImage(_ name: String) -> Image { + if let image = assetUIImage(name) { + return Image(uiImage: image) + } + return Image(systemName: "questionmark.square.dashed") + } + + private func assetUIImage(_ name: String) -> UIImage? { + UIImage( + named: name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) + } + + private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { + (value / source) * target + } + + private func scaledWidth(_ value: CGFloat, in size: CGSize) -> CGFloat { + scaled(value, from: canvasSize.width, to: size.width) + } + + private func scaledHeight(_ value: CGFloat, in size: CGSize) -> CGFloat { + scaled(value, from: canvasSize.height, to: size.height) + } + } + + private enum KeyboardColumn { + case feature + case numbersLeft + case numbersMiddle + case numbersRight + case operators + case utility + + var x: CGFloat { + switch self { + case .feature: 0 + case .numbersLeft: 50 + case .numbersMiddle: 99.6667 + case .numbersRight: 149.3333 + case .operators: 199 + case .utility: 248 + } + } + + var width: CGFloat { + switch self { + case .feature, .numbersMiddle, .numbersRight: 49.6667 + case .numbersLeft, .operators: 49 + case .utility: 72 + } + } + } + + private enum KeyboardRow { + case top + case upperMiddle + case lowerMiddle + case bottom + + var y: CGFloat { + switch self { + case .top: 0 + case .upperMiddle: 45 + case .lowerMiddle: 90 + case .bottom: 135 + } + } + + var height: CGFloat { 45 } + } + + private struct KeyboardKey: Identifiable { + let id = UUID() + let action: () -> Void + let frame: CGRect + let enabled: Bool + let pressedAsset: String? + let accessibilityLabel: String + + static func text( + _ label: String, + action: @escaping () -> Void, + frame: CGRect, + enabled: Bool, + pressedAsset: String = "Keyboard-grey-pressed" + ) -> KeyboardKey { + KeyboardKey( + action: action, + frame: frame, + enabled: enabled, + pressedAsset: pressedAsset, + accessibilityLabel: label + ) + } + + static func custom( + action: @escaping () -> Void, + frame: CGRect, + enabled: Bool, + pressedAsset: String, + accessibilityLabel: String + ) -> KeyboardKey { + KeyboardKey( + action: action, + frame: frame, + enabled: enabled, + pressedAsset: pressedAsset, + accessibilityLabel: accessibilityLabel + ) + } + } + + private struct KeyboardLabel: Identifiable { + enum Content { + case text(TextStyle) + case image(ImageStyle) + } + + struct TextStyle { + let value: String + let color: Color + let fontName: String + let fontSize: CGFloat + let bottomInset: CGFloat + let offset: CGSize + } + + struct ImageStyle { + let name: String + let insets: UIEdgeInsets + let offset: CGSize + } + + let id = UUID() + let content: Content + let frame: CGRect + + static func text( + _ value: String, + frame: CGRect, + color: Color, + fontName: String, + fontSize: CGFloat, + bottomInset: CGFloat = 0, + offset: CGSize = .zero + ) -> KeyboardLabel { + KeyboardLabel( + content: .text( + TextStyle( + value: value, + color: color, + fontName: fontName, + fontSize: fontSize, + bottomInset: bottomInset, + offset: offset + ) + ), + frame: frame + ) + } + + static func image( + _ name: String, + frame: CGRect, + imageInsets: UIEdgeInsets, + imageOffset: CGSize = .zero + ) -> KeyboardLabel { + KeyboardLabel( + content: .image( + ImageStyle( + name: name, + insets: imageInsets, + offset: imageOffset + ) + ), + frame: frame + ) + } + } + + private struct StretchedKeyboardButton: View { + let key: KeyboardKey + let canvasSize: CGSize + let containerSize: CGSize + @ViewBuilder let pressedOverlay: () -> PressedOverlay + + var body: some View { + Button(action: key.action) { + Rectangle() + .fill(Color.white.opacity(0.001)) + .contentShape(Rectangle()) + } + .buttonStyle( + KeyboardTapTargetStyle( + pressedOverlay: AnyView(pressedOverlay()) + ) + ) + .disabled(!key.enabled) + .frame( + width: scaled(key.frame.width, from: canvasSize.width, to: containerSize.width), + height: scaled(key.frame.height, from: canvasSize.height, to: containerSize.height) + ) + .position( + x: scaled(key.frame.midX, from: canvasSize.width, to: containerSize.width), + y: scaled(key.frame.midY, from: canvasSize.height, to: containerSize.height) + ) + .accessibility(label: Text(key.accessibilityLabel)) + } + + private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { + (value / source) * target + } + } + + private struct KeyboardTapTargetStyle: ButtonStyle { + let pressedOverlay: AnyView + + func makeBody(configuration: Configuration) -> some View { + ZStack { + configuration.label + if configuration.isPressed { + pressedOverlay + } + } + } + } + +#endif diff --git a/Package.swift b/Package.swift index 4f78dbc..9f1716e 100644 --- a/Package.swift +++ b/Package.swift @@ -13,9 +13,6 @@ let package = Package( .library( name: "MathKeyboard", targets: ["MathKeyboard"]), - .library( - name: "MathKeyboardSwiftUI", - targets: ["MathKeyboardSwiftUI"]), ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") @@ -39,11 +36,6 @@ let package = Package( .headerSearchPath("./keyboard") ] ), - .target( - name: "MathKeyboardSwiftUI", - dependencies: ["MathKeyboard", "MathEditor"], - path: "./mathKeyboardSwiftUI" - ), .testTarget( name: "MathEditorTests", dependencies: ["MathEditor"], diff --git a/mathKeyboardSwiftUI/KeyboardContainerView.swift b/mathKeyboardSwiftUI/KeyboardContainerView.swift deleted file mode 100644 index d8293ca..0000000 --- a/mathKeyboardSwiftUI/KeyboardContainerView.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// KeyboardContainerView.swift -// MathEditor -// -// Created by Madiyar Aitbayev on 22/03/2026. -// - -#if os(iOS) - -import MathEditor -import MathKeyboard -import SwiftUI -import UIKit - -struct KeyboardContainerView: UIViewRepresentable { - let state: KeyboardState - weak var textInput: (any UIView & UIKeyInput)? - - func makeUIView(context: Context) -> KeyboardContainerUIView { - let view = KeyboardContainerUIView() - view.sync( - state: state, - editingTarget: textInput - ) - return view - } - - func updateUIView(_ uiView: KeyboardContainerUIView, context: Context) { - uiView.sync( - state: state, - editingTarget: textInput - ) - } -} - -final class KeyboardContainerUIView: UIView { - private weak var currentKeyboard: UIView? - private lazy var keyboards: [KeyboardTab: (UIView & KeyboardConfigurable)] = [ - .numbers: makeNumbersKeyboard(), - .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), - .operations: makeKeyboard(named: KeyboardTab.operations.nibName), - .functions: makeKeyboard(named: KeyboardTab.functions.nibName), - .letters: makeKeyboard(named: KeyboardTab.letters.nibName), - ] - - fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { - for keyboard in keyboards.values { - keyboard.setEditingTarget(editingTarget) - keyboard.setEqualsState(state.equalsAllowed) - keyboard.setFractionState(state.fractionsAllowed) - keyboard.setVariablesState(state.variablesAllowed) - keyboard.setNumbersState(state.numbersAllowed) - keyboard.setOperatorState(state.operatorsAllowed) - keyboard.setExponentState(state.exponentHighlighted) - keyboard.setSquareRootState(state.squareRootHighlighted) - keyboard.setRadicalState(state.radicalHighlighted) - } - - display(keyboard: keyboards[state.currentTab]!) - } - - fileprivate func display(keyboard: UIView) { - guard currentKeyboard !== keyboard else { return } - - currentKeyboard?.removeFromSuperview() - currentKeyboard = keyboard - addSubview(keyboard) - - NSLayoutConstraint.activate([ - keyboard.topAnchor.constraint(equalTo: topAnchor), - keyboard.leadingAnchor.constraint(equalTo: leadingAnchor), - keyboard.trailingAnchor.constraint(equalTo: trailingAnchor), - keyboard.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - } - - private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { - let keyboard = NumbersKeyboardHostView() - keyboard.translatesAutoresizingMaskIntoConstraints = false - return keyboard - } - - private func makeKeyboard(named nibName: String) -> UIView & KeyboardConfigurable { - let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() - let keyboard = UINib(nibName: nibName, bundle: bundle) - .instantiate(withOwner: nil, options: nil) - .compactMap { $0 as? MTKeyboard } - .first! - keyboard.translatesAutoresizingMaskIntoConstraints = false - return keyboard - } -} - -#endif // os(iOS) diff --git a/mathKeyboardSwiftUI/MathKeyboardRootView.swift b/mathKeyboardSwiftUI/MathKeyboardRootView.swift deleted file mode 100644 index 163461a..0000000 --- a/mathKeyboardSwiftUI/MathKeyboardRootView.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// MathKeyboardRootView.swift -// MathEditor -// -// Created by Madiyar Aitbayev on 22/03/2026. -// - -import MathKeyboard -import SwiftUI - -#if os(iOS) - -public struct MathKeyboardRootView: View { - let state: KeyboardState - weak var textInput: (any UIView & UIKeyInput)? - let onTabSelected: (KeyboardTab) -> Void - - public var body: some View { - GeometryReader { proxy in - let totalHeight = proxy.size.height - let tabHeight = totalHeight / 5.0 - let keyboardHeight = totalHeight - tabHeight - - VStack(spacing: 0) { - HStack(spacing: 0) { - ForEach(KeyboardTab.allCases) { tab in - Button { - onTabSelected(tab) - } label: { - if let image = tabImage(for: tab) { - Image(uiImage: image) - .renderingMode(.original) - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(.horizontal, 8) - .padding(.vertical, 6) - } else { - Text(tab.title ?? "") - .font(.system(size: 14, weight: state.currentTab == tab ? .semibold : .regular)) - .foregroundColor(.black) - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - } - .buttonStyle(.plain) - .background(Color(white: 0.768627451)) - } - } - .frame(height: tabHeight) - - KeyboardContainerView( - state: state, - textInput: textInput - ) - .frame(height: keyboardHeight) - } - .background(Color.white) - .edgesIgnoringSafeArea(.all) - } - } - - private func tabImage(for tab: KeyboardTab) -> UIImage? { - guard let names = tab.imageNames else { - return nil - } - let name = state.currentTab == tab ? names.selected : names.normal - return UIImage( - named: name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) - } -} - -#endif // os(iOS) diff --git a/mathKeyboardSwiftUI/NumbersKeyboardView.swift b/mathKeyboardSwiftUI/NumbersKeyboardView.swift deleted file mode 100644 index e315875..0000000 --- a/mathKeyboardSwiftUI/NumbersKeyboardView.swift +++ /dev/null @@ -1,570 +0,0 @@ -#if os(iOS) - -import MathEditor -import MathKeyboard -import SwiftUI -import CoreText -import Foundation -import UIKit - -protocol KeyboardConfigurable: AnyObject { - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) - func setNumbersState(_ enabled: Bool) - func setOperatorState(_ enabled: Bool) - func setVariablesState(_ enabled: Bool) - func setFractionState(_ enabled: Bool) - func setEqualsState(_ enabled: Bool) - func setExponentState(_ highlighted: Bool) - func setSquareRootState(_ highlighted: Bool) - func setRadicalState(_ highlighted: Bool) -} - -extension MTKeyboard: KeyboardConfigurable { - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { - self.textView = textView - } -} - -final class NumbersKeyboardModel: ObservableObject { - @Published var numbersAllowed = true - @Published var operatorsAllowed = true - @Published var variablesAllowed = true - @Published var fractionsAllowed = true - @Published var equalsAllowed = true - @Published var exponentHighlighted = false -} - -private enum KeyboardFontRegistry { - static let variableFontName: String = { - guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { - return "HelveticaNeue" - } - guard - let fontURL = bundle.url(forResource: "lmroman10-bolditalic", withExtension: "otf"), - let provider = CGDataProvider(url: fontURL as CFURL), - let font = CGFont(provider) - else { - return "HelveticaNeue" - } - - let postScriptName = font.postScriptName as String? ?? "HelveticaNeue" - var error: Unmanaged? - CTFontManagerRegisterGraphicsFont(font, &error) - return postScriptName - }() -} - -final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { - private let model = NumbersKeyboardModel() - private weak var editingTarget: (any UIView & UIKeyInput)? - private lazy var hostingController = UIHostingController( - rootView: NumbersKeyboardView( - model: model, - onInsertText: { [weak self] text in self?.insert(text) }, - onBackspace: { [weak self] in self?.backspace() }, - onDismiss: { [weak self] in self?.dismissKeyboard() } - ) - ) - - override init(frame: CGRect) { - super.init(frame: frame) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { - editingTarget = textView - } - - func setNumbersState(_ enabled: Bool) { - updateModel(\.numbersAllowed, to: enabled) - } - - func setOperatorState(_ enabled: Bool) { - updateModel(\.operatorsAllowed, to: enabled) - } - - func setVariablesState(_ enabled: Bool) { - updateModel(\.variablesAllowed, to: enabled) - } - - func setFractionState(_ enabled: Bool) { - updateModel(\.fractionsAllowed, to: enabled) - } - - func setEqualsState(_ enabled: Bool) { - updateModel(\.equalsAllowed, to: enabled) - } - - func setExponentState(_ highlighted: Bool) { - updateModel(\.exponentHighlighted, to: highlighted) - } - - func setSquareRootState(_ highlighted: Bool) {} - - func setRadicalState(_ highlighted: Bool) {} - - var enableInputClicksWhenVisible: Bool { true } - - private func commonInit() { - backgroundColor = .white - - let hostedView = hostingController.view! - if #available(iOS 16.4, *) { - hostingController.safeAreaRegions = [] - } - hostedView.backgroundColor = .clear - hostedView.translatesAutoresizingMaskIntoConstraints = false - addSubview(hostedView) - - NSLayoutConstraint.activate([ - hostedView.topAnchor.constraint(equalTo: topAnchor), - hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), - hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), - hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - } - - private func insert(_ text: String) { - playClickForCustomKeyTap() - editingTarget?.insertText(text) - } - - private func backspace() { - playClickForCustomKeyTap() - editingTarget?.deleteBackward() - } - - private func dismissKeyboard() { - playClickForCustomKeyTap() - editingTarget?.resignFirstResponder() - } - - private func playClickForCustomKeyTap() { - UIDevice.current.playInputClick() - } - - private func updateModel( - _ keyPath: ReferenceWritableKeyPath, - to value: Bool - ) { - guard model[keyPath: keyPath] != value else { return } - - DispatchQueue.main.async { [weak self] in - guard let self, self.model[keyPath: keyPath] != value else { return } - self.model[keyPath: keyPath] = value - } - } -} - -struct NumbersKeyboardView: View { - @ObservedObject var model: NumbersKeyboardModel - let onInsertText: (String) -> Void - let onBackspace: () -> Void - let onDismiss: () -> Void - - private let canvasSize = CGSize(width: 320, height: 180) - - var body: some View { - GeometryReader { proxy in - stretchedKeyboardCanvas(in: proxy.size) - .frame(width: proxy.size.width, height: proxy.size.height, alignment: .topLeading) - .clipped() - } - } - - private func stretchedKeyboardCanvas(in size: CGSize) -> some View { - ZStack(alignment: .topLeading) { - assetImage("Numbers Keyboard") - .resizable() - .frame(width: size.width, height: size.height) - - if model.exponentHighlighted { - stretchedOverlayImage("blue-button-highlighted", frame: keyboardFrame(column: .feature, row: .bottom, width: 50), in: size) - } - - if !model.equalsAllowed { - stretchedOverlayImage("num-button-disabled", frame: keyboardFrame(column: .numbersRight, row: .bottom), in: size) - } - - ForEach(keys) { key in - stretchedTappableKey(key, in: size) - } - - ForEach(labels) { label in - stretchedKeyboardLabel(label, in: size) - } - } - } - - private var keys: [KeyboardKey] { - [ - .text("x", action: { onInsertText("x") }, frame: keyboardFrame(column: .feature, row: .top), enabled: model.variablesAllowed), - .text("y", action: { onInsertText("y") }, frame: keyboardFrame(column: .feature, row: .upperMiddle), enabled: model.variablesAllowed), - .custom(action: { onInsertText(MTSymbolFractionSlash) }, frame: keyboardFrame(column: .feature, row: .lowerMiddle), enabled: model.fractionsAllowed, pressedAsset: "Keyboard-marine-pressed", accessibilityLabel: "Fraction"), - .custom(action: { onInsertText("^") }, frame: keyboardFrame(column: .feature, row: .bottom), enabled: true, pressedAsset: "Keyboard-marine-pressed", accessibilityLabel: "Exponent"), - - .text("7", action: { onInsertText("7") }, frame: keyboardFrame(column: .numbersLeft, row: .top), enabled: model.numbersAllowed), - .text("8", action: { onInsertText("8") }, frame: keyboardFrame(column: .numbersMiddle, row: .top), enabled: model.numbersAllowed), - .text("9", action: { onInsertText("9") }, frame: keyboardFrame(column: .numbersRight, row: .top), enabled: model.numbersAllowed), - .text("4", action: { onInsertText("4") }, frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), enabled: model.numbersAllowed), - .text("5", action: { onInsertText("5") }, frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), enabled: model.numbersAllowed), - .text("6", action: { onInsertText("6") }, frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), enabled: model.numbersAllowed), - .text("1", action: { onInsertText("1") }, frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), enabled: model.numbersAllowed), - .text("2", action: { onInsertText("2") }, frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), enabled: model.numbersAllowed), - .text("3", action: { onInsertText("3") }, frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), enabled: model.numbersAllowed), - .text("0", action: { onInsertText("0") }, frame: keyboardFrame(column: .numbersLeft, row: .bottom), enabled: model.numbersAllowed), - .text(".", action: { onInsertText(".") }, frame: keyboardFrame(column: .numbersMiddle, row: .bottom), enabled: model.numbersAllowed), - .text("=", action: { onInsertText("=") }, frame: keyboardFrame(column: .numbersRight, row: .bottom), enabled: model.equalsAllowed), - .text("÷", action: { onInsertText("÷") }, frame: keyboardFrame(column: .operators, row: .top), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text("×", action: { onInsertText("×") }, frame: keyboardFrame(column: .operators, row: .upperMiddle), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text("-", action: { onInsertText("-") }, frame: keyboardFrame(column: .operators, row: .lowerMiddle), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text("+", action: { onInsertText("+") }, frame: keyboardFrame(column: .operators, row: .bottom), enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .custom(action: onBackspace, frame: keyboardFrame(column: .utility, row: .top), enabled: true, pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Backspace"), - .custom(action: { onInsertText("\n") }, frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), enabled: true, pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Enter"), - .custom(action: onDismiss, frame: keyboardFrame(column: .utility, row: .bottom), enabled: true, pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Dismiss keyboard"), - ] - } - - private var labels: [KeyboardLabel] { - [ - .text("x", frame: keyboardFrame(column: .feature, row: .top), color: .white, fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), - .text("y", frame: keyboardFrame(column: .feature, row: .upperMiddle), color: .white, fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), - .image("Fraction", frame: keyboardFrame(column: .feature, row: .lowerMiddle), imageInsets: UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 0)), - .image("Exponent", frame: keyboardFrame(column: .feature, row: .bottom), imageInsets: .zero), - - .text("7", frame: keyboardFrame(column: .numbersLeft, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("8", frame: keyboardFrame(column: .numbersMiddle, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("9", frame: keyboardFrame(column: .numbersRight, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("4", frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("5", frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("6", frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("1", frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("2", frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("3", frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("0", frame: keyboardFrame(column: .numbersLeft, row: .bottom), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text(".", frame: keyboardFrame(column: .numbersMiddle, row: .bottom), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20), - .text("=", frame: keyboardFrame(column: .numbersRight, row: .bottom), color: model.equalsAllowed ? .black : Color(white: 0.67), fontName: "HelveticaNeue-Thin", fontSize: 25, bottomInset: 2), - .text("÷", frame: keyboardFrame(column: .operators, row: .top), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), - .text("×", frame: keyboardFrame(column: .operators, row: .upperMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 7), - .text("-", frame: keyboardFrame(column: .operators, row: .lowerMiddle), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 25, bottomInset: 9), - .text("+", frame: keyboardFrame(column: .operators, row: .bottom), color: .black, fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), - .image("Backspace", frame: keyboardFrame(column: .utility, row: .top), imageInsets: .zero), - .text("Enter", frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), color: .white, fontName: "HelveticaNeue-Light", fontSize: 20), - .image("Keyboard Down", frame: keyboardFrame(column: .utility, row: .bottom), imageInsets: UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0), imageOffset: CGSize(width: 0, height: -1.5)), - ] - } - - private func keyboardFrame( - column: KeyboardColumn, - row: KeyboardRow, - rowSpan: CGFloat = 1, - width: CGFloat? = nil - ) -> CGRect { - CGRect( - x: column.x, - y: row.y, - width: width ?? column.width, - height: row.height * rowSpan - ) - } - - private func stretchedTappableKey(_ key: KeyboardKey, in size: CGSize) -> some View { - StretchedKeyboardButton( - key: key, - canvasSize: canvasSize, - containerSize: size, - pressedOverlay: { - if let asset = key.pressedAsset { - assetImage(asset) - .resizable() - } else { - Color.clear - } - } - ) - } - - private func stretchedOverlayImage(_ name: String, frame: CGRect, in size: CGSize) -> some View { - assetImage(name) - .resizable() - .frame(width: scaledWidth(frame.width, in: size), height: scaledHeight(frame.height, in: size)) - .position( - x: scaled(frame.midX, from: canvasSize.width, to: size.width), - y: scaled(frame.midY, from: canvasSize.height, to: size.height) - ) - .allowsHitTesting(false) - } - - @ViewBuilder - private func stretchedKeyboardLabel(_ label: KeyboardLabel, in size: CGSize) -> some View { - switch label.content { - case .text(let style): - Text(style.value) - .font(.custom(style.fontName, size: style.fontSize)) - .foregroundColor(style.color) - .frame(width: scaledWidth(label.frame.width, in: size), height: scaledHeight(label.frame.height, in: size)) - .position( - x: scaled(label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), - y: scaled(label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) - (style.bottomInset / 2) - ) - .allowsHitTesting(false) - - case .image(let style): - if let image = assetUIImage(style.name) { - let contentWidth = scaled(label.frame.width - style.insets.left - style.insets.right, from: canvasSize.width, to: size.width) - let contentHeight = scaled(label.frame.height - style.insets.top - style.insets.bottom, from: canvasSize.height, to: size.height) - let intrinsicWidth = image.size.width - let intrinsicHeight = image.size.height - let fitScale = min(contentWidth / intrinsicWidth, contentHeight / intrinsicHeight, 1) - let renderedWidth = intrinsicWidth * fitScale - let renderedHeight = intrinsicHeight * fitScale - - Image(uiImage: image) - .renderingMode(.original) - .resizable() - .frame(width: renderedWidth, height: renderedHeight) - .position( - x: scaled(label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), - y: scaled(label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) - ) - .allowsHitTesting(false) - } - } - } - - private func assetImage(_ name: String) -> Image { - if let image = assetUIImage(name) { - return Image(uiImage: image) - } - return Image(systemName: "questionmark.square.dashed") - } - - private func assetUIImage(_ name: String) -> UIImage? { - UIImage( - named: name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) - } - - private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { - (value / source) * target - } - - private func scaledWidth(_ value: CGFloat, in size: CGSize) -> CGFloat { - scaled(value, from: canvasSize.width, to: size.width) - } - - private func scaledHeight(_ value: CGFloat, in size: CGSize) -> CGFloat { - scaled(value, from: canvasSize.height, to: size.height) - } -} - -private enum KeyboardColumn { - case feature - case numbersLeft - case numbersMiddle - case numbersRight - case operators - case utility - - var x: CGFloat { - switch self { - case .feature: 0 - case .numbersLeft: 50 - case .numbersMiddle: 99.6667 - case .numbersRight: 149.3333 - case .operators: 199 - case .utility: 248 - } - } - - var width: CGFloat { - switch self { - case .feature, .numbersMiddle, .numbersRight: 49.6667 - case .numbersLeft, .operators: 49 - case .utility: 72 - } - } -} - -private enum KeyboardRow { - case top - case upperMiddle - case lowerMiddle - case bottom - - var y: CGFloat { - switch self { - case .top: 0 - case .upperMiddle: 45 - case .lowerMiddle: 90 - case .bottom: 135 - } - } - - var height: CGFloat { 45 } -} - -private struct KeyboardKey: Identifiable { - let id = UUID() - let action: () -> Void - let frame: CGRect - let enabled: Bool - let pressedAsset: String? - let accessibilityLabel: String - - static func text( - _ label: String, - action: @escaping () -> Void, - frame: CGRect, - enabled: Bool, - pressedAsset: String = "Keyboard-grey-pressed" - ) -> KeyboardKey { - KeyboardKey( - action: action, - frame: frame, - enabled: enabled, - pressedAsset: pressedAsset, - accessibilityLabel: label - ) - } - - static func custom( - action: @escaping () -> Void, - frame: CGRect, - enabled: Bool, - pressedAsset: String, - accessibilityLabel: String - ) -> KeyboardKey { - KeyboardKey( - action: action, - frame: frame, - enabled: enabled, - pressedAsset: pressedAsset, - accessibilityLabel: accessibilityLabel - ) - } -} - -private struct KeyboardLabel: Identifiable { - enum Content { - case text(TextStyle) - case image(ImageStyle) - } - - struct TextStyle { - let value: String - let color: Color - let fontName: String - let fontSize: CGFloat - let bottomInset: CGFloat - let offset: CGSize - } - - struct ImageStyle { - let name: String - let insets: UIEdgeInsets - let offset: CGSize - } - - let id = UUID() - let content: Content - let frame: CGRect - - static func text( - _ value: String, - frame: CGRect, - color: Color, - fontName: String, - fontSize: CGFloat, - bottomInset: CGFloat = 0, - offset: CGSize = .zero - ) -> KeyboardLabel { - KeyboardLabel( - content: .text( - TextStyle( - value: value, - color: color, - fontName: fontName, - fontSize: fontSize, - bottomInset: bottomInset, - offset: offset - ) - ), - frame: frame - ) - } - - static func image( - _ name: String, - frame: CGRect, - imageInsets: UIEdgeInsets, - imageOffset: CGSize = .zero - ) -> KeyboardLabel { - KeyboardLabel( - content: .image( - ImageStyle( - name: name, - insets: imageInsets, - offset: imageOffset - ) - ), - frame: frame - ) - } -} - -private struct StretchedKeyboardButton: View { - let key: KeyboardKey - let canvasSize: CGSize - let containerSize: CGSize - @ViewBuilder let pressedOverlay: () -> PressedOverlay - - var body: some View { - Button(action: key.action) { - Rectangle() - .fill(Color.white.opacity(0.001)) - .contentShape(Rectangle()) - } - .buttonStyle( - KeyboardTapTargetStyle( - pressedOverlay: AnyView(pressedOverlay()) - ) - ) - .disabled(!key.enabled) - .frame( - width: scaled(key.frame.width, from: canvasSize.width, to: containerSize.width), - height: scaled(key.frame.height, from: canvasSize.height, to: containerSize.height) - ) - .position( - x: scaled(key.frame.midX, from: canvasSize.width, to: containerSize.width), - y: scaled(key.frame.midY, from: canvasSize.height, to: containerSize.height) - ) - .accessibility(label: Text(key.accessibilityLabel)) - } - - private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { - (value / source) * target - } -} - -private struct KeyboardTapTargetStyle: ButtonStyle { - let pressedOverlay: AnyView - - func makeBody(configuration: Configuration) -> some View { - ZStack { - configuration.label - if configuration.isPressed { - pressedOverlay - } - } - } -} - -#endif From e3f5889ecc3e40262935f6c07d67ff39cf59ed9f Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 09:26:39 +0000 Subject: [PATCH 055/133] iOS 17 --- MathKeyboardSwiftUI/Package.swift | 4 ++-- .../NumbersKeyboardView.swift | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/MathKeyboardSwiftUI/Package.swift b/MathKeyboardSwiftUI/Package.swift index 94e5dc7..de036e3 100644 --- a/MathKeyboardSwiftUI/Package.swift +++ b/MathKeyboardSwiftUI/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 5.9 import PackageDescription let package = Package( name: "MathKeyboardSwiftUI", defaultLocalization: "en", - platforms: [.iOS(.v16), .macOS(.v11)], + platforms: [.iOS(.v17), .macOS(.v11)], products: [ .library( name: "MathKeyboardSwiftUI", diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index bc3b4fc..bb770d5 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -2,6 +2,7 @@ import MathEditor import MathKeyboard + import Observation import SwiftUI import CoreText import Foundation @@ -25,13 +26,14 @@ } } - final class NumbersKeyboardModel: ObservableObject { - @Published var numbersAllowed = true - @Published var operatorsAllowed = true - @Published var variablesAllowed = true - @Published var fractionsAllowed = true - @Published var equalsAllowed = true - @Published var exponentHighlighted = false + @Observable + final class NumbersKeyboardModel { + var numbersAllowed = true + var operatorsAllowed = true + var variablesAllowed = true + var fractionsAllowed = true + var equalsAllowed = true + var exponentHighlighted = false } private enum KeyboardFontRegistry { @@ -162,7 +164,7 @@ } struct NumbersKeyboardView: View { - @ObservedObject var model: NumbersKeyboardModel + let model: NumbersKeyboardModel let onInsertText: (String) -> Void let onBackspace: () -> Void let onDismiss: () -> Void From e988d1cb50aba9ae5a72c2f736cf9342e7a0957e Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 14:15:42 +0000 Subject: [PATCH 056/133] Adjust equal-grid utility column to 22.5 percent width --- .../KeyboardContainerView.swift | 7 +- .../MathKeyboardSwiftUI/KeyboardTab.swift | 4 + .../NumbersKeyboardView.swift | 78 ++++++++++++------- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index 5190473..0adcf6b 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -36,7 +36,8 @@ final class KeyboardContainerUIView: UIView { private weak var currentKeyboard: UIView? private lazy var keyboards: [KeyboardTab: (UIView & KeyboardConfigurable)] = [ - .numbers: makeNumbersKeyboard(), + .numbers: makeNumbersKeyboard(layout: .legacy), + .numbersGrid: makeNumbersKeyboard(layout: .equalGrid), .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), .operations: makeKeyboard(named: KeyboardTab.operations.nibName), .functions: makeKeyboard(named: KeyboardTab.functions.nibName), @@ -74,8 +75,8 @@ ]) } - private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { - let keyboard = NumbersKeyboardHostView() + private func makeNumbersKeyboard(layout: NumbersKeyboardLayout) -> UIView & KeyboardConfigurable { + let keyboard = NumbersKeyboardHostView(layout: layout) keyboard.translatesAutoresizingMaskIntoConstraints = false return keyboard } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift index d80e94e..a55426b 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift @@ -7,6 +7,7 @@ enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable { case numbers + case numbersGrid case legacyNumbers case operations case functions @@ -19,6 +20,7 @@ extension KeyboardTab { var imageNames: (normal: String, selected: String)? { switch self { case .numbers: return ("Numbers Symbol wbg", "Number Symbol") + case .numbersGrid: return nil case .legacyNumbers: return nil case .operations: return ("Operations Symbol wbg", "Operations Symbol") case .functions: return ("Functions Symbol wbg", "Functions Symbol") @@ -29,6 +31,7 @@ extension KeyboardTab { var nibName: String { switch self { case .numbers: return "MTKeyboard" + case .numbersGrid: return "MTKeyboard" case .legacyNumbers: return "MTKeyboard" case .operations: return "MTKeyboardTab2" case .functions: return "MTKeyboardTab3" @@ -38,6 +41,7 @@ extension KeyboardTab { var title: String? { switch self { + case .numbersGrid: return "Grid" case .legacyNumbers: return "Old" default: return nil } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index bb770d5..b145a95 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -36,6 +36,11 @@ var exponentHighlighted = false } + enum NumbersKeyboardLayout { + case legacy + case equalGrid + } + private enum KeyboardFontRegistry { static let variableFontName: String = { guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { @@ -58,22 +63,18 @@ final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { private let model = NumbersKeyboardModel() + private let layout: NumbersKeyboardLayout private weak var editingTarget: (any UIView & UIKeyInput)? - private lazy var hostingController = UIHostingController( - rootView: NumbersKeyboardView( - model: model, - onInsertText: { [weak self] text in self?.insert(text) }, - onBackspace: { [weak self] in self?.backspace() }, - onDismiss: { [weak self] in self?.dismissKeyboard() } - ) - ) + private lazy var hostingController = UIHostingController(rootView: makeRootView()) - override init(frame: CGRect) { + init(layout: NumbersKeyboardLayout, frame: CGRect = .zero) { + self.layout = layout super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { + layout = .legacy super.init(coder: coder) commonInit() } @@ -131,6 +132,16 @@ ]) } + private func makeRootView() -> NumbersKeyboardView { + NumbersKeyboardView( + model: model, + layout: layout, + onInsertText: { [weak self] text in self?.insert(text) }, + onBackspace: { [weak self] in self?.backspace() }, + onDismiss: { [weak self] in self?.dismissKeyboard() } + ) + } + private func insert(_ text: String) { playClickForCustomKeyTap() editingTarget?.insertText(text) @@ -165,6 +176,7 @@ struct NumbersKeyboardView: View { let model: NumbersKeyboardModel + let layout: NumbersKeyboardLayout let onInsertText: (String) -> Void let onBackspace: () -> Void let onDismiss: () -> Void @@ -187,8 +199,7 @@ if model.exponentHighlighted { stretchedOverlayImage( - "blue-button-highlighted", - frame: keyboardFrame(column: .feature, row: .bottom, width: 50), in: size) + "blue-button-highlighted", frame: keyboardFrame(column: .feature, row: .bottom), in: size) } if !model.equalsAllowed { @@ -376,9 +387,9 @@ width: CGFloat? = nil ) -> CGRect { CGRect( - x: column.x, + x: column.layout(layout).x, y: row.y, - width: width ?? column.width, + width: width ?? column.layout(layout).width, height: row.height * rowSpan ) } @@ -498,22 +509,31 @@ case operators case utility - var x: CGFloat { - switch self { - case .feature: 0 - case .numbersLeft: 50 - case .numbersMiddle: 99.6667 - case .numbersRight: 149.3333 - case .operators: 199 - case .utility: 248 - } - } - - var width: CGFloat { - switch self { - case .feature, .numbersMiddle, .numbersRight: 49.6667 - case .numbersLeft, .operators: 49 - case .utility: 72 + struct Frame { + let x: CGFloat + let width: CGFloat + } + + func layout(_ mode: NumbersKeyboardLayout) -> Frame { + switch mode { + case .legacy: + switch self { + case .feature: Frame(x: 0, width: 49.6667) + case .numbersLeft: Frame(x: 50, width: 49) + case .numbersMiddle: Frame(x: 99.6667, width: 49.6667) + case .numbersRight: Frame(x: 149.3333, width: 49.6667) + case .operators: Frame(x: 199, width: 49) + case .utility: Frame(x: 248, width: 72) + } + case .equalGrid: + switch self { + case .feature: Frame(x: 0, width: 49.6) + case .numbersLeft: Frame(x: 49.6, width: 49.6) + case .numbersMiddle: Frame(x: 99.2, width: 49.6) + case .numbersRight: Frame(x: 148.8, width: 49.6) + case .operators: Frame(x: 198.4, width: 49.6) + case .utility: Frame(x: 248, width: 72) + } } } } From ca8fb5fc17ac214ae354e773892ba7ef21190317 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 14:26:43 +0000 Subject: [PATCH 057/133] Make equal-grid numbers layout the default and remove extra tab --- .../KeyboardContainerView.swift | 7 ++- .../MathKeyboardSwiftUI/KeyboardTab.swift | 4 -- .../NumbersKeyboardView.swift | 44 +++++-------------- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index 0adcf6b..5190473 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -36,8 +36,7 @@ final class KeyboardContainerUIView: UIView { private weak var currentKeyboard: UIView? private lazy var keyboards: [KeyboardTab: (UIView & KeyboardConfigurable)] = [ - .numbers: makeNumbersKeyboard(layout: .legacy), - .numbersGrid: makeNumbersKeyboard(layout: .equalGrid), + .numbers: makeNumbersKeyboard(), .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), .operations: makeKeyboard(named: KeyboardTab.operations.nibName), .functions: makeKeyboard(named: KeyboardTab.functions.nibName), @@ -75,8 +74,8 @@ ]) } - private func makeNumbersKeyboard(layout: NumbersKeyboardLayout) -> UIView & KeyboardConfigurable { - let keyboard = NumbersKeyboardHostView(layout: layout) + private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { + let keyboard = NumbersKeyboardHostView() keyboard.translatesAutoresizingMaskIntoConstraints = false return keyboard } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift index a55426b..d80e94e 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift @@ -7,7 +7,6 @@ enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable { case numbers - case numbersGrid case legacyNumbers case operations case functions @@ -20,7 +19,6 @@ extension KeyboardTab { var imageNames: (normal: String, selected: String)? { switch self { case .numbers: return ("Numbers Symbol wbg", "Number Symbol") - case .numbersGrid: return nil case .legacyNumbers: return nil case .operations: return ("Operations Symbol wbg", "Operations Symbol") case .functions: return ("Functions Symbol wbg", "Functions Symbol") @@ -31,7 +29,6 @@ extension KeyboardTab { var nibName: String { switch self { case .numbers: return "MTKeyboard" - case .numbersGrid: return "MTKeyboard" case .legacyNumbers: return "MTKeyboard" case .operations: return "MTKeyboardTab2" case .functions: return "MTKeyboardTab3" @@ -41,7 +38,6 @@ extension KeyboardTab { var title: String? { switch self { - case .numbersGrid: return "Grid" case .legacyNumbers: return "Old" default: return nil } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index b145a95..fabb8f3 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -36,11 +36,6 @@ var exponentHighlighted = false } - enum NumbersKeyboardLayout { - case legacy - case equalGrid - } - private enum KeyboardFontRegistry { static let variableFontName: String = { guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { @@ -63,18 +58,15 @@ final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { private let model = NumbersKeyboardModel() - private let layout: NumbersKeyboardLayout private weak var editingTarget: (any UIView & UIKeyInput)? private lazy var hostingController = UIHostingController(rootView: makeRootView()) - init(layout: NumbersKeyboardLayout, frame: CGRect = .zero) { - self.layout = layout + override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { - layout = .legacy super.init(coder: coder) commonInit() } @@ -135,7 +127,6 @@ private func makeRootView() -> NumbersKeyboardView { NumbersKeyboardView( model: model, - layout: layout, onInsertText: { [weak self] text in self?.insert(text) }, onBackspace: { [weak self] in self?.backspace() }, onDismiss: { [weak self] in self?.dismissKeyboard() } @@ -176,7 +167,6 @@ struct NumbersKeyboardView: View { let model: NumbersKeyboardModel - let layout: NumbersKeyboardLayout let onInsertText: (String) -> Void let onBackspace: () -> Void let onDismiss: () -> Void @@ -387,9 +377,9 @@ width: CGFloat? = nil ) -> CGRect { CGRect( - x: column.layout(layout).x, + x: column.layout().x, y: row.y, - width: width ?? column.layout(layout).width, + width: width ?? column.layout().width, height: row.height * rowSpan ) } @@ -514,26 +504,14 @@ let width: CGFloat } - func layout(_ mode: NumbersKeyboardLayout) -> Frame { - switch mode { - case .legacy: - switch self { - case .feature: Frame(x: 0, width: 49.6667) - case .numbersLeft: Frame(x: 50, width: 49) - case .numbersMiddle: Frame(x: 99.6667, width: 49.6667) - case .numbersRight: Frame(x: 149.3333, width: 49.6667) - case .operators: Frame(x: 199, width: 49) - case .utility: Frame(x: 248, width: 72) - } - case .equalGrid: - switch self { - case .feature: Frame(x: 0, width: 49.6) - case .numbersLeft: Frame(x: 49.6, width: 49.6) - case .numbersMiddle: Frame(x: 99.2, width: 49.6) - case .numbersRight: Frame(x: 148.8, width: 49.6) - case .operators: Frame(x: 198.4, width: 49.6) - case .utility: Frame(x: 248, width: 72) - } + func layout() -> Frame { + switch self { + case .feature: Frame(x: 0, width: 49.6) + case .numbersLeft: Frame(x: 49.6, width: 49.6) + case .numbersMiddle: Frame(x: 99.2, width: 49.6) + case .numbersRight: Frame(x: 148.8, width: 49.6) + case .operators: Frame(x: 198.4, width: 49.6) + case .utility: Frame(x: 248, width: 72) } } } From 1c414b33001df3d9e27b912bdf28f3b33dd804f8 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 14:28:51 +0000 Subject: [PATCH 058/133] Refactor numbers keyboard to SwiftUI stack-based layout --- .../NumbersKeyboardView.swift | 601 +++++------------- 1 file changed, 152 insertions(+), 449 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index fabb8f3..b7898c8 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -171,523 +171,226 @@ let onBackspace: () -> Void let onDismiss: () -> Void - private let canvasSize = CGSize(width: 320, height: 180) - var body: some View { GeometryReader { proxy in - stretchedKeyboardCanvas(in: proxy.size) - .frame(width: proxy.size.width, height: proxy.size.height, alignment: .topLeading) - .clipped() + let totalWidth = proxy.size.width + let totalHeight = proxy.size.height + let utilityWidth = totalWidth * 0.225 + let standardColumnWidth = (totalWidth - utilityWidth) / 5 + let rowHeight = totalHeight / 4 + + HStack(spacing: 0) { + keyboardColumn(items: featureItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: numbersLeftItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: numbersMiddleItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: numbersRightItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: operatorItems, width: standardColumnWidth, rowHeight: rowHeight) + utilityColumn(width: utilityWidth, rowHeight: rowHeight) + } + .frame(width: totalWidth, height: totalHeight) + .background(.white) } } - private func stretchedKeyboardCanvas(in size: CGSize) -> some View { - ZStack(alignment: .topLeading) { - assetImage("Numbers Keyboard") - .resizable() - .frame(width: size.width, height: size.height) - - if model.exponentHighlighted { - stretchedOverlayImage( - "blue-button-highlighted", frame: keyboardFrame(column: .feature, row: .bottom), in: size) - } - - if !model.equalsAllowed { - stretchedOverlayImage( - "num-button-disabled", frame: keyboardFrame(column: .numbersRight, row: .bottom), - in: size) - } - - ForEach(keys) { key in - stretchedTappableKey(key, in: size) - } - - ForEach(labels) { label in - stretchedKeyboardLabel(label, in: size) + private func keyboardColumn(items: [KeyboardCell], width: CGFloat, rowHeight: CGFloat) -> some View { + VStack(spacing: 0) { + ForEach(items) { item in + keyButton(item) + .frame(width: width, height: rowHeight) } } } - private var keys: [KeyboardKey] { - [ - .text( - "x", action: { onInsertText("x") }, frame: keyboardFrame(column: .feature, row: .top), - enabled: model.variablesAllowed), - .text( - "y", action: { onInsertText("y") }, - frame: keyboardFrame(column: .feature, row: .upperMiddle), enabled: model.variablesAllowed - ), - .custom( - action: { onInsertText(MTSymbolFractionSlash) }, - frame: keyboardFrame(column: .feature, row: .lowerMiddle), - enabled: model.fractionsAllowed, pressedAsset: "Keyboard-marine-pressed", - accessibilityLabel: "Fraction"), - .custom( - action: { onInsertText("^") }, frame: keyboardFrame(column: .feature, row: .bottom), - enabled: true, pressedAsset: "Keyboard-marine-pressed", accessibilityLabel: "Exponent"), + private func utilityColumn(width: CGFloat, rowHeight: CGFloat) -> some View { + VStack(spacing: 0) { + keyButton(utilityBackspace) + .frame(width: width, height: rowHeight) + keyButton(utilityEnter) + .frame(width: width, height: rowHeight * 2) + keyButton(utilityDismiss) + .frame(width: width, height: rowHeight) + } + } - .text( - "7", action: { onInsertText("7") }, frame: keyboardFrame(column: .numbersLeft, row: .top), - enabled: model.numbersAllowed), - .text( - "8", action: { onInsertText("8") }, - frame: keyboardFrame(column: .numbersMiddle, row: .top), enabled: model.numbersAllowed), - .text( - "9", action: { onInsertText("9") }, - frame: keyboardFrame(column: .numbersRight, row: .top), enabled: model.numbersAllowed), - .text( - "4", action: { onInsertText("4") }, - frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), - enabled: model.numbersAllowed), - .text( - "5", action: { onInsertText("5") }, - frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), - enabled: model.numbersAllowed), - .text( - "6", action: { onInsertText("6") }, - frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), - enabled: model.numbersAllowed), - .text( - "1", action: { onInsertText("1") }, - frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), - enabled: model.numbersAllowed), - .text( - "2", action: { onInsertText("2") }, - frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), - enabled: model.numbersAllowed), - .text( - "3", action: { onInsertText("3") }, - frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), - enabled: model.numbersAllowed), - .text( - "0", action: { onInsertText("0") }, - frame: keyboardFrame(column: .numbersLeft, row: .bottom), enabled: model.numbersAllowed), - .text( - ".", action: { onInsertText(".") }, - frame: keyboardFrame(column: .numbersMiddle, row: .bottom), enabled: model.numbersAllowed), - .text( - "=", action: { onInsertText("=") }, - frame: keyboardFrame(column: .numbersRight, row: .bottom), enabled: model.equalsAllowed), - .text( - "÷", action: { onInsertText("÷") }, frame: keyboardFrame(column: .operators, row: .top), - enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text( - "×", action: { onInsertText("×") }, - frame: keyboardFrame(column: .operators, row: .upperMiddle), - enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text( - "-", action: { onInsertText("-") }, - frame: keyboardFrame(column: .operators, row: .lowerMiddle), - enabled: model.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text( - "+", action: { onInsertText("+") }, - frame: keyboardFrame(column: .operators, row: .bottom), enabled: model.operatorsAllowed, - pressedAsset: "Keyboard-orange-pressed"), - .custom( - action: onBackspace, frame: keyboardFrame(column: .utility, row: .top), enabled: true, - pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Backspace"), - .custom( - action: { onInsertText("\n") }, - frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), enabled: true, - pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Enter"), - .custom( - action: onDismiss, frame: keyboardFrame(column: .utility, row: .bottom), enabled: true, - pressedAsset: "Keyboard-grey-pressed", accessibilityLabel: "Dismiss keyboard"), - ] + private func keyButton(_ cell: KeyboardCell) -> some View { + Button(action: cell.action) { + ZStack { + Rectangle().fill(cell.backgroundColor) + cell.label + } + } + .buttonStyle(.plain) + .disabled(!cell.enabled) + .opacity(cell.enabled ? 1 : 0.6) + .overlay( + Rectangle() + .stroke(Color.black.opacity(0.08), lineWidth: 0.5) + ) + .accessibilityLabel(cell.accessibilityLabel) } - private var labels: [KeyboardLabel] { + private var featureItems: [KeyboardCell] { [ .text( - "x", frame: keyboardFrame(column: .feature, row: .top), color: .white, - fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), + label: "x", foreground: .white, background: .blueKey, fontName: KeyboardFontRegistry.variableFontName, + action: { onInsertText("x") }, enabled: model.variablesAllowed), .text( - "y", frame: keyboardFrame(column: .feature, row: .upperMiddle), color: .white, - fontName: KeyboardFontRegistry.variableFontName, fontSize: 20, bottomInset: 10), + label: "y", foreground: .white, background: .blueKey, fontName: KeyboardFontRegistry.variableFontName, + action: { onInsertText("y") }, enabled: model.variablesAllowed), .image( - "Fraction", frame: keyboardFrame(column: .feature, row: .lowerMiddle), - imageInsets: UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 0)), - .image( - "Exponent", frame: keyboardFrame(column: .feature, row: .bottom), imageInsets: .zero), - - .text( - "7", frame: keyboardFrame(column: .numbersLeft, row: .top), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "8", frame: keyboardFrame(column: .numbersMiddle, row: .top), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "9", frame: keyboardFrame(column: .numbersRight, row: .top), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "4", frame: keyboardFrame(column: .numbersLeft, row: .upperMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "5", frame: keyboardFrame(column: .numbersMiddle, row: .upperMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "6", frame: keyboardFrame(column: .numbersRight, row: .upperMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "1", frame: keyboardFrame(column: .numbersLeft, row: .lowerMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "2", frame: keyboardFrame(column: .numbersMiddle, row: .lowerMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "3", frame: keyboardFrame(column: .numbersRight, row: .lowerMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "0", frame: keyboardFrame(column: .numbersLeft, row: .bottom), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - ".", frame: keyboardFrame(column: .numbersMiddle, row: .bottom), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20), - .text( - "=", frame: keyboardFrame(column: .numbersRight, row: .bottom), - color: model.equalsAllowed ? .black : Color(white: 0.67), fontName: "HelveticaNeue-Thin", - fontSize: 25, bottomInset: 2), - .text( - "÷", frame: keyboardFrame(column: .operators, row: .top), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), - .text( - "×", frame: keyboardFrame(column: .operators, row: .upperMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 7), - .text( - "-", frame: keyboardFrame(column: .operators, row: .lowerMiddle), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 25, bottomInset: 9), - .text( - "+", frame: keyboardFrame(column: .operators, row: .bottom), color: .black, - fontName: "HelveticaNeue-Thin", fontSize: 20, bottomInset: 5), - .image("Backspace", frame: keyboardFrame(column: .utility, row: .top), imageInsets: .zero), - .text( - "Enter", frame: keyboardFrame(column: .utility, row: .upperMiddle, rowSpan: 2), - color: .white, fontName: "HelveticaNeue-Light", fontSize: 20), + imageName: "Fraction", background: .blueKey, + action: { onInsertText(MTSymbolFractionSlash) }, enabled: model.fractionsAllowed, + accessibilityLabel: "Fraction"), .image( - "Keyboard Down", frame: keyboardFrame(column: .utility, row: .bottom), - imageInsets: UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0), - imageOffset: CGSize(width: 0, height: -1.5)), + imageName: "Exponent", background: model.exponentHighlighted ? .highlightBlue : .blueKey, + action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent"), ] } - private func keyboardFrame( - column: KeyboardColumn, - row: KeyboardRow, - rowSpan: CGFloat = 1, - width: CGFloat? = nil - ) -> CGRect { - CGRect( - x: column.layout().x, - y: row.y, - width: width ?? column.layout().width, - height: row.height * rowSpan - ) - } - - private func stretchedTappableKey(_ key: KeyboardKey, in size: CGSize) -> some View { - StretchedKeyboardButton( - key: key, - canvasSize: canvasSize, - containerSize: size, - pressedOverlay: { - if let asset = key.pressedAsset { - assetImage(asset) - .resizable() - } else { - Color.clear - } - } - ) - } - - private func stretchedOverlayImage(_ name: String, frame: CGRect, in size: CGSize) -> some View - { - assetImage(name) - .resizable() - .frame( - width: scaledWidth(frame.width, in: size), height: scaledHeight(frame.height, in: size) - ) - .position( - x: scaled(frame.midX, from: canvasSize.width, to: size.width), - y: scaled(frame.midY, from: canvasSize.height, to: size.height) - ) - .allowsHitTesting(false) - } - - @ViewBuilder - private func stretchedKeyboardLabel(_ label: KeyboardLabel, in size: CGSize) -> some View { - switch label.content { - case .text(let style): - Text(style.value) - .font(.custom(style.fontName, size: style.fontSize)) - .foregroundColor(style.color) - .frame( - width: scaledWidth(label.frame.width, in: size), - height: scaledHeight(label.frame.height, in: size) - ) - .position( - x: scaled( - label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), - y: scaled( - label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) - - (style.bottomInset / 2) - ) - .allowsHitTesting(false) - - case .image(let style): - if let image = assetUIImage(style.name) { - let contentWidth = scaled( - label.frame.width - style.insets.left - style.insets.right, from: canvasSize.width, - to: size.width) - let contentHeight = scaled( - label.frame.height - style.insets.top - style.insets.bottom, from: canvasSize.height, - to: size.height) - let intrinsicWidth = image.size.width - let intrinsicHeight = image.size.height - let fitScale = min(contentWidth / intrinsicWidth, contentHeight / intrinsicHeight, 1) - let renderedWidth = intrinsicWidth * fitScale - let renderedHeight = intrinsicHeight * fitScale - - Image(uiImage: image) - .renderingMode(.original) - .resizable() - .frame(width: renderedWidth, height: renderedHeight) - .position( - x: scaled( - label.frame.midX + style.offset.width, from: canvasSize.width, to: size.width), - y: scaled( - label.frame.midY + style.offset.height, from: canvasSize.height, to: size.height) - ) - .allowsHitTesting(false) - } - } + private var numbersLeftItems: [KeyboardCell] { + numberColumn(["7", "4", "1", "0"]) } - private func assetImage(_ name: String) -> Image { - if let image = assetUIImage(name) { - return Image(uiImage: image) - } - return Image(systemName: "questionmark.square.dashed") + private var numbersMiddleItems: [KeyboardCell] { + numberColumn(["8", "5", "2", "."]) } - private func assetUIImage(_ name: String) -> UIImage? { - UIImage( - named: name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) + private var numbersRightItems: [KeyboardCell] { + [ + .number("9", action: { onInsertText("9") }, enabled: model.numbersAllowed), + .number("6", action: { onInsertText("6") }, enabled: model.numbersAllowed), + .number("3", action: { onInsertText("3") }, enabled: model.numbersAllowed), + .text( + label: "=", foreground: model.equalsAllowed ? .black : Color(white: 0.67), background: .numberKey, + action: { onInsertText("=") }, enabled: model.equalsAllowed), + ] } - private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { - (value / source) * target + private var operatorItems: [KeyboardCell] { + [ + .operator("÷", action: { onInsertText("÷") }, enabled: model.operatorsAllowed), + .operator("×", action: { onInsertText("×") }, enabled: model.operatorsAllowed), + .operator("-", action: { onInsertText("-") }, enabled: model.operatorsAllowed), + .operator("+", action: { onInsertText("+") }, enabled: model.operatorsAllowed), + ] } - private func scaledWidth(_ value: CGFloat, in size: CGSize) -> CGFloat { - scaled(value, from: canvasSize.width, to: size.width) + private var utilityBackspace: KeyboardCell { + .image( + imageName: "Backspace", background: .utilityKey, + action: onBackspace, enabled: true, accessibilityLabel: "Backspace") } - private func scaledHeight(_ value: CGFloat, in size: CGSize) -> CGFloat { - scaled(value, from: canvasSize.height, to: size.height) + private var utilityEnter: KeyboardCell { + .text( + label: "Enter", foreground: .white, background: .utilityKey, + action: { onInsertText("\n") }, enabled: true) } - } - private enum KeyboardColumn { - case feature - case numbersLeft - case numbersMiddle - case numbersRight - case operators - case utility - - struct Frame { - let x: CGFloat - let width: CGFloat - } - - func layout() -> Frame { - switch self { - case .feature: Frame(x: 0, width: 49.6) - case .numbersLeft: Frame(x: 49.6, width: 49.6) - case .numbersMiddle: Frame(x: 99.2, width: 49.6) - case .numbersRight: Frame(x: 148.8, width: 49.6) - case .operators: Frame(x: 198.4, width: 49.6) - case .utility: Frame(x: 248, width: 72) - } + private var utilityDismiss: KeyboardCell { + .image( + imageName: "Keyboard Down", background: .utilityKey, + action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard") } - } - private enum KeyboardRow { - case top - case upperMiddle - case lowerMiddle - case bottom - - var y: CGFloat { - switch self { - case .top: 0 - case .upperMiddle: 45 - case .lowerMiddle: 90 - case .bottom: 135 + private func numberColumn(_ values: [String]) -> [KeyboardCell] { + values.map { value in + .number(value, action: { onInsertText(value) }, enabled: model.numbersAllowed) } } - var height: CGFloat { 45 } } - private struct KeyboardKey: Identifiable { + private struct KeyboardCell: Identifiable { let id = UUID() + let label: AnyView + let backgroundColor: Color let action: () -> Void - let frame: CGRect let enabled: Bool - let pressedAsset: String? let accessibilityLabel: String static func text( - _ label: String, + label: String, + foreground: Color, + background: Color, + fontName: String = "HelveticaNeue-Thin", + fontSize: CGFloat = 20, action: @escaping () -> Void, - frame: CGRect, enabled: Bool, - pressedAsset: String = "Keyboard-grey-pressed" - ) -> KeyboardKey { - KeyboardKey( + accessibilityLabel: String? = nil + ) -> KeyboardCell { + KeyboardCell( + label: AnyView( + Text(label) + .font(.custom(fontName, size: fontSize)) + .foregroundColor(foreground) + ), + backgroundColor: background, action: action, - frame: frame, enabled: enabled, - pressedAsset: pressedAsset, - accessibilityLabel: label + accessibilityLabel: accessibilityLabel ?? label ) } - static func custom( + static func image( + imageName: String, + background: Color, action: @escaping () -> Void, - frame: CGRect, enabled: Bool, - pressedAsset: String, accessibilityLabel: String - ) -> KeyboardKey { - KeyboardKey( + ) -> KeyboardCell { + KeyboardCell( + label: AnyView( + Group { + if let uiImage = UIImage( + named: imageName, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) { + Image(uiImage: uiImage) + .renderingMode(.original) + .resizable() + .scaledToFit() + .padding(8) + } else { + Image(systemName: "questionmark.square.dashed") + } + } + ), + backgroundColor: background, action: action, - frame: frame, enabled: enabled, - pressedAsset: pressedAsset, accessibilityLabel: accessibilityLabel ) } - } - - private struct KeyboardLabel: Identifiable { - enum Content { - case text(TextStyle) - case image(ImageStyle) - } - - struct TextStyle { - let value: String - let color: Color - let fontName: String - let fontSize: CGFloat - let bottomInset: CGFloat - let offset: CGSize - } - - struct ImageStyle { - let name: String - let insets: UIEdgeInsets - let offset: CGSize - } - - let id = UUID() - let content: Content - let frame: CGRect - - static func text( - _ value: String, - frame: CGRect, - color: Color, - fontName: String, - fontSize: CGFloat, - bottomInset: CGFloat = 0, - offset: CGSize = .zero - ) -> KeyboardLabel { - KeyboardLabel( - content: .text( - TextStyle( - value: value, - color: color, - fontName: fontName, - fontSize: fontSize, - bottomInset: bottomInset, - offset: offset - ) - ), - frame: frame - ) - } - static func image( - _ name: String, - frame: CGRect, - imageInsets: UIEdgeInsets, - imageOffset: CGSize = .zero - ) -> KeyboardLabel { - KeyboardLabel( - content: .image( - ImageStyle( - name: name, - insets: imageInsets, - offset: imageOffset - ) - ), - frame: frame + static func number(_ value: String, action: @escaping () -> Void, enabled: Bool) -> KeyboardCell { + .text( + label: value, + foreground: .black, + background: .numberKey, + action: action, + enabled: enabled ) } - } - - private struct StretchedKeyboardButton: View { - let key: KeyboardKey - let canvasSize: CGSize - let containerSize: CGSize - @ViewBuilder let pressedOverlay: () -> PressedOverlay - var body: some View { - Button(action: key.action) { - Rectangle() - .fill(Color.white.opacity(0.001)) - .contentShape(Rectangle()) - } - .buttonStyle( - KeyboardTapTargetStyle( - pressedOverlay: AnyView(pressedOverlay()) - ) - ) - .disabled(!key.enabled) - .frame( - width: scaled(key.frame.width, from: canvasSize.width, to: containerSize.width), - height: scaled(key.frame.height, from: canvasSize.height, to: containerSize.height) - ) - .position( - x: scaled(key.frame.midX, from: canvasSize.width, to: containerSize.width), - y: scaled(key.frame.midY, from: canvasSize.height, to: containerSize.height) + static func `operator`(_ value: String, action: @escaping () -> Void, enabled: Bool) -> KeyboardCell { + .text( + label: value, + foreground: .black, + background: .operatorKey, + action: action, + enabled: enabled ) - .accessibility(label: Text(key.accessibilityLabel)) - } - - private func scaled(_ value: CGFloat, from source: CGFloat, to target: CGFloat) -> CGFloat { - (value / source) * target } } - private struct KeyboardTapTargetStyle: ButtonStyle { - let pressedOverlay: AnyView - - func makeBody(configuration: Configuration) -> some View { - ZStack { - configuration.label - if configuration.isPressed { - pressedOverlay - } - } - } + private extension Color { + static let numberKey = Color(white: 0.95) + static let operatorKey = Color(red: 0.99, green: 0.90, blue: 0.78) + static let blueKey = Color(red: 0.57, green: 0.78, blue: 0.95) + static let highlightBlue = Color(red: 0.41, green: 0.65, blue: 0.90) + static let utilityKey = Color(red: 0.53, green: 0.53, blue: 0.55) } #endif From e3920e617d1b0b4c859bb6015137905b93bf2d32 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 14:41:47 +0000 Subject: [PATCH 059/133] Restore image-based keyboard background in SwiftUI numbers layout --- .../NumbersKeyboardView.swift | 94 ++++++++++--------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index b7898c8..4a7b10b 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -179,16 +179,21 @@ let standardColumnWidth = (totalWidth - utilityWidth) / 5 let rowHeight = totalHeight / 4 - HStack(spacing: 0) { - keyboardColumn(items: featureItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: numbersLeftItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: numbersMiddleItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: numbersRightItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: operatorItems, width: standardColumnWidth, rowHeight: rowHeight) - utilityColumn(width: utilityWidth, rowHeight: rowHeight) + ZStack { + assetImage("Numbers Keyboard") + .resizable() + .frame(width: totalWidth, height: totalHeight) + + HStack(spacing: 0) { + keyboardColumn(items: featureItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: numbersLeftItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: numbersMiddleItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: numbersRightItems, width: standardColumnWidth, rowHeight: rowHeight) + keyboardColumn(items: operatorItems, width: standardColumnWidth, rowHeight: rowHeight) + utilityColumn(width: utilityWidth, rowHeight: rowHeight) + } } .frame(width: totalWidth, height: totalHeight) - .background(.white) } } @@ -215,35 +220,36 @@ private func keyButton(_ cell: KeyboardCell) -> some View { Button(action: cell.action) { ZStack { - Rectangle().fill(cell.backgroundColor) + Rectangle().fill(Color.white.opacity(0.001)) + if let overlayAsset = cell.overlayAsset { + assetImage(overlayAsset) + .resizable() + } cell.label } } .buttonStyle(.plain) .disabled(!cell.enabled) - .opacity(cell.enabled ? 1 : 0.6) - .overlay( - Rectangle() - .stroke(Color.black.opacity(0.08), lineWidth: 0.5) - ) + .opacity(cell.enabled ? 1 : 0.75) .accessibilityLabel(cell.accessibilityLabel) } private var featureItems: [KeyboardCell] { [ .text( - label: "x", foreground: .white, background: .blueKey, fontName: KeyboardFontRegistry.variableFontName, + label: "x", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, action: { onInsertText("x") }, enabled: model.variablesAllowed), .text( - label: "y", foreground: .white, background: .blueKey, fontName: KeyboardFontRegistry.variableFontName, + label: "y", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, action: { onInsertText("y") }, enabled: model.variablesAllowed), .image( - imageName: "Fraction", background: .blueKey, + imageName: "Fraction", action: { onInsertText(MTSymbolFractionSlash) }, enabled: model.fractionsAllowed, accessibilityLabel: "Fraction"), .image( - imageName: "Exponent", background: model.exponentHighlighted ? .highlightBlue : .blueKey, - action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent"), + imageName: "Exponent", + action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent", + overlayAsset: model.exponentHighlighted ? "blue-button-highlighted" : nil), ] } @@ -261,8 +267,9 @@ .number("6", action: { onInsertText("6") }, enabled: model.numbersAllowed), .number("3", action: { onInsertText("3") }, enabled: model.numbersAllowed), .text( - label: "=", foreground: model.equalsAllowed ? .black : Color(white: 0.67), background: .numberKey, - action: { onInsertText("=") }, enabled: model.equalsAllowed), + label: "=", foreground: model.equalsAllowed ? .black : Color(white: 0.67), + action: { onInsertText("=") }, enabled: model.equalsAllowed, + overlayAsset: model.equalsAllowed ? nil : "num-button-disabled"), ] } @@ -277,19 +284,19 @@ private var utilityBackspace: KeyboardCell { .image( - imageName: "Backspace", background: .utilityKey, + imageName: "Backspace", action: onBackspace, enabled: true, accessibilityLabel: "Backspace") } private var utilityEnter: KeyboardCell { .text( - label: "Enter", foreground: .white, background: .utilityKey, + label: "Enter", foreground: .white, action: { onInsertText("\n") }, enabled: true) } private var utilityDismiss: KeyboardCell { .image( - imageName: "Keyboard Down", background: .utilityKey, + imageName: "Keyboard Down", action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard") } @@ -299,25 +306,36 @@ } } + private func assetImage(_ name: String) -> Image { + if let image = UIImage( + named: name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) { + return Image(uiImage: image) + } + return Image(systemName: "questionmark.square.dashed") + } + } private struct KeyboardCell: Identifiable { let id = UUID() let label: AnyView - let backgroundColor: Color let action: () -> Void let enabled: Bool let accessibilityLabel: String + let overlayAsset: String? static func text( label: String, foreground: Color, - background: Color, fontName: String = "HelveticaNeue-Thin", fontSize: CGFloat = 20, action: @escaping () -> Void, enabled: Bool, - accessibilityLabel: String? = nil + accessibilityLabel: String? = nil, + overlayAsset: String? = nil ) -> KeyboardCell { KeyboardCell( label: AnyView( @@ -325,19 +343,19 @@ .font(.custom(fontName, size: fontSize)) .foregroundColor(foreground) ), - backgroundColor: background, action: action, enabled: enabled, - accessibilityLabel: accessibilityLabel ?? label + accessibilityLabel: accessibilityLabel ?? label, + overlayAsset: overlayAsset ) } static func image( imageName: String, - background: Color, action: @escaping () -> Void, enabled: Bool, - accessibilityLabel: String + accessibilityLabel: String, + overlayAsset: String? = nil ) -> KeyboardCell { KeyboardCell( label: AnyView( @@ -357,10 +375,10 @@ } } ), - backgroundColor: background, action: action, enabled: enabled, - accessibilityLabel: accessibilityLabel + accessibilityLabel: accessibilityLabel, + overlayAsset: overlayAsset ) } @@ -368,7 +386,6 @@ .text( label: value, foreground: .black, - background: .numberKey, action: action, enabled: enabled ) @@ -378,19 +395,10 @@ .text( label: value, foreground: .black, - background: .operatorKey, action: action, enabled: enabled ) } } - private extension Color { - static let numberKey = Color(white: 0.95) - static let operatorKey = Color(red: 0.99, green: 0.90, blue: 0.78) - static let blueKey = Color(red: 0.57, green: 0.78, blue: 0.95) - static let highlightBlue = Color(red: 0.41, green: 0.65, blue: 0.90) - static let utilityKey = Color(red: 0.53, green: 0.53, blue: 0.55) - } - #endif From f60974b14859bab8509a1b8ef295a20bef34daf1 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:11:03 +0000 Subject: [PATCH 060/133] Split numbers keyboard into feature, main, and utility sections --- .../NumbersKeyboardView.swift | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 4a7b10b..91add0f 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -185,18 +185,35 @@ .frame(width: totalWidth, height: totalHeight) HStack(spacing: 0) { - keyboardColumn(items: featureItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: numbersLeftItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: numbersMiddleItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: numbersRightItems, width: standardColumnWidth, rowHeight: rowHeight) - keyboardColumn(items: operatorItems, width: standardColumnWidth, rowHeight: rowHeight) - utilityColumn(width: utilityWidth, rowHeight: rowHeight) + featureSection(columnWidth: standardColumnWidth, rowHeight: rowHeight) + mainColumnsSection(columnWidth: standardColumnWidth, rowHeight: rowHeight) + utilitySection(width: utilityWidth, rowHeight: rowHeight) } } .frame(width: totalWidth, height: totalHeight) } } + // 1) Feature column + private func featureSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { + keyboardColumn(items: featureItems, width: columnWidth, rowHeight: rowHeight) + } + + // 2) Reusable main four-column block (numbers left/middle/right + operators) + private func mainColumnsSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { + KeyboardColumnsSection( + columns: [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems], + columnWidth: columnWidth, + rowHeight: rowHeight, + cellContent: { keyButton($0) } + ) + } + + // 3) Utility column + private func utilitySection(width: CGFloat, rowHeight: CGFloat) -> some View { + utilityColumn(width: width, rowHeight: rowHeight) + } + private func keyboardColumn(items: [KeyboardCell], width: CGFloat, rowHeight: CGFloat) -> some View { VStack(spacing: 0) { ForEach(items) { item in @@ -401,4 +418,24 @@ } } + private struct KeyboardColumnsSection: View { + let columns: [[KeyboardCell]] + let columnWidth: CGFloat + let rowHeight: CGFloat + let cellContent: (KeyboardCell) -> CellContent + + var body: some View { + HStack(spacing: 0) { + ForEach(Array(columns.enumerated()), id: \.offset) { _, column in + VStack(spacing: 0) { + ForEach(column) { cell in + cellContent(cell) + .frame(width: columnWidth, height: rowHeight) + } + } + } + } + } + } + #endif From 208756efe3ad369e65f0efc29d0197ad597212aa Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:11:36 +0000 Subject: [PATCH 061/133] Use Grid for main numeric columns and inline feature/utility sections --- .../NumbersKeyboardView.swift | 81 ++++++------------- 1 file changed, 24 insertions(+), 57 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 91add0f..1597478 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -185,55 +185,38 @@ .frame(width: totalWidth, height: totalHeight) HStack(spacing: 0) { - featureSection(columnWidth: standardColumnWidth, rowHeight: rowHeight) + VStack(spacing: 0) { + keyButton(featureItems[0]).frame(width: standardColumnWidth, height: rowHeight) + keyButton(featureItems[1]).frame(width: standardColumnWidth, height: rowHeight) + keyButton(featureItems[2]).frame(width: standardColumnWidth, height: rowHeight) + keyButton(featureItems[3]).frame(width: standardColumnWidth, height: rowHeight) + } mainColumnsSection(columnWidth: standardColumnWidth, rowHeight: rowHeight) - utilitySection(width: utilityWidth, rowHeight: rowHeight) + VStack(spacing: 0) { + keyButton(utilityBackspace).frame(width: utilityWidth, height: rowHeight) + keyButton(utilityEnter).frame(width: utilityWidth, height: rowHeight * 2) + keyButton(utilityDismiss).frame(width: utilityWidth, height: rowHeight) + } } } .frame(width: totalWidth, height: totalHeight) } } - // 1) Feature column - private func featureSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { - keyboardColumn(items: featureItems, width: columnWidth, rowHeight: rowHeight) - } - - // 2) Reusable main four-column block (numbers left/middle/right + operators) + // 2) Main four-column block (numbers left/middle/right + operators) using Grid private func mainColumnsSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { - KeyboardColumnsSection( - columns: [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems], - columnWidth: columnWidth, - rowHeight: rowHeight, - cellContent: { keyButton($0) } - ) - } - - // 3) Utility column - private func utilitySection(width: CGFloat, rowHeight: CGFloat) -> some View { - utilityColumn(width: width, rowHeight: rowHeight) - } - - private func keyboardColumn(items: [KeyboardCell], width: CGFloat, rowHeight: CGFloat) -> some View { - VStack(spacing: 0) { - ForEach(items) { item in - keyButton(item) - .frame(width: width, height: rowHeight) + Grid(horizontalSpacing: 0, verticalSpacing: 0) { + ForEach(0..<4, id: \.self) { row in + GridRow { + ForEach(0..<4, id: \.self) { column in + keyButton(mainColumns[column][row]) + .frame(width: columnWidth, height: rowHeight) + } + } } } } - private func utilityColumn(width: CGFloat, rowHeight: CGFloat) -> some View { - VStack(spacing: 0) { - keyButton(utilityBackspace) - .frame(width: width, height: rowHeight) - keyButton(utilityEnter) - .frame(width: width, height: rowHeight * 2) - keyButton(utilityDismiss) - .frame(width: width, height: rowHeight) - } - } - private func keyButton(_ cell: KeyboardCell) -> some View { Button(action: cell.action) { ZStack { @@ -299,6 +282,10 @@ ] } + private var mainColumns: [[KeyboardCell]] { + [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] + } + private var utilityBackspace: KeyboardCell { .image( imageName: "Backspace", @@ -418,24 +405,4 @@ } } - private struct KeyboardColumnsSection: View { - let columns: [[KeyboardCell]] - let columnWidth: CGFloat - let rowHeight: CGFloat - let cellContent: (KeyboardCell) -> CellContent - - var body: some View { - HStack(spacing: 0) { - ForEach(Array(columns.enumerated()), id: \.offset) { _, column in - VStack(spacing: 0) { - ForEach(column) { cell in - cellContent(cell) - .frame(width: columnWidth, height: rowHeight) - } - } - } - } - } - } - #endif From bdf967bf2a26f7526ce154c38a92facc2096a9f9 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:14:12 +0000 Subject: [PATCH 062/133] Simplify numbers keyboard data model and grid wiring --- .../NumbersKeyboardView.swift | 58 +++++++------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 1597478..95a9972 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -205,11 +205,12 @@ // 2) Main four-column block (numbers left/middle/right + operators) using Grid private func mainColumnsSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { + let columns = [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] Grid(horizontalSpacing: 0, verticalSpacing: 0) { ForEach(0..<4, id: \.self) { row in GridRow { ForEach(0..<4, id: \.self) { column in - keyButton(mainColumns[column][row]) + keyButton(columns[column][row]) .frame(width: columnWidth, height: rowHeight) } } @@ -254,18 +255,28 @@ } private var numbersLeftItems: [KeyboardCell] { - numberColumn(["7", "4", "1", "0"]) + [ + .text(label: "7", foreground: .black, action: { onInsertText("7") }, enabled: model.numbersAllowed), + .text(label: "4", foreground: .black, action: { onInsertText("4") }, enabled: model.numbersAllowed), + .text(label: "1", foreground: .black, action: { onInsertText("1") }, enabled: model.numbersAllowed), + .text(label: "0", foreground: .black, action: { onInsertText("0") }, enabled: model.numbersAllowed), + ] } private var numbersMiddleItems: [KeyboardCell] { - numberColumn(["8", "5", "2", "."]) + [ + .text(label: "8", foreground: .black, action: { onInsertText("8") }, enabled: model.numbersAllowed), + .text(label: "5", foreground: .black, action: { onInsertText("5") }, enabled: model.numbersAllowed), + .text(label: "2", foreground: .black, action: { onInsertText("2") }, enabled: model.numbersAllowed), + .text(label: ".", foreground: .black, action: { onInsertText(".") }, enabled: model.numbersAllowed), + ] } private var numbersRightItems: [KeyboardCell] { [ - .number("9", action: { onInsertText("9") }, enabled: model.numbersAllowed), - .number("6", action: { onInsertText("6") }, enabled: model.numbersAllowed), - .number("3", action: { onInsertText("3") }, enabled: model.numbersAllowed), + .text(label: "9", foreground: .black, action: { onInsertText("9") }, enabled: model.numbersAllowed), + .text(label: "6", foreground: .black, action: { onInsertText("6") }, enabled: model.numbersAllowed), + .text(label: "3", foreground: .black, action: { onInsertText("3") }, enabled: model.numbersAllowed), .text( label: "=", foreground: model.equalsAllowed ? .black : Color(white: 0.67), action: { onInsertText("=") }, enabled: model.equalsAllowed, @@ -275,17 +286,13 @@ private var operatorItems: [KeyboardCell] { [ - .operator("÷", action: { onInsertText("÷") }, enabled: model.operatorsAllowed), - .operator("×", action: { onInsertText("×") }, enabled: model.operatorsAllowed), - .operator("-", action: { onInsertText("-") }, enabled: model.operatorsAllowed), - .operator("+", action: { onInsertText("+") }, enabled: model.operatorsAllowed), + .text(label: "÷", foreground: .black, action: { onInsertText("÷") }, enabled: model.operatorsAllowed), + .text(label: "×", foreground: .black, action: { onInsertText("×") }, enabled: model.operatorsAllowed), + .text(label: "-", foreground: .black, action: { onInsertText("-") }, enabled: model.operatorsAllowed), + .text(label: "+", foreground: .black, action: { onInsertText("+") }, enabled: model.operatorsAllowed), ] } - private var mainColumns: [[KeyboardCell]] { - [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] - } - private var utilityBackspace: KeyboardCell { .image( imageName: "Backspace", @@ -304,12 +311,6 @@ action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard") } - private func numberColumn(_ values: [String]) -> [KeyboardCell] { - values.map { value in - .number(value, action: { onInsertText(value) }, enabled: model.numbersAllowed) - } - } - private func assetImage(_ name: String) -> Image { if let image = UIImage( named: name, @@ -386,23 +387,6 @@ ) } - static func number(_ value: String, action: @escaping () -> Void, enabled: Bool) -> KeyboardCell { - .text( - label: value, - foreground: .black, - action: action, - enabled: enabled - ) - } - - static func `operator`(_ value: String, action: @escaping () -> Void, enabled: Bool) -> KeyboardCell { - .text( - label: value, - foreground: .black, - action: action, - enabled: enabled - ) - } } #endif From bde52575294ea838be163b4cae1b01f2d786732c Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:36:18 +0000 Subject: [PATCH 063/133] Use KeyboardState snapshot inside NumbersKeyboardView --- .../NumbersKeyboardView.swift | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 95a9972..67b8a59 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -170,6 +170,20 @@ let onInsertText: (String) -> Void let onBackspace: () -> Void let onDismiss: () -> Void + + private var keyboardState: KeyboardState { + KeyboardState( + currentTab: .numbers, + equalsAllowed: model.equalsAllowed, + fractionsAllowed: model.fractionsAllowed, + variablesAllowed: model.variablesAllowed, + numbersAllowed: model.numbersAllowed, + operatorsAllowed: model.operatorsAllowed, + exponentHighlighted: model.exponentHighlighted, + squareRootHighlighted: false, + radicalHighlighted: false + ) + } var body: some View { GeometryReader { proxy in @@ -239,57 +253,57 @@ [ .text( label: "x", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("x") }, enabled: model.variablesAllowed), + action: { onInsertText("x") }, enabled: keyboardState.variablesAllowed), .text( label: "y", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("y") }, enabled: model.variablesAllowed), + action: { onInsertText("y") }, enabled: keyboardState.variablesAllowed), .image( imageName: "Fraction", - action: { onInsertText(MTSymbolFractionSlash) }, enabled: model.fractionsAllowed, + action: { onInsertText(MTSymbolFractionSlash) }, enabled: keyboardState.fractionsAllowed, accessibilityLabel: "Fraction"), .image( imageName: "Exponent", action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent", - overlayAsset: model.exponentHighlighted ? "blue-button-highlighted" : nil), + overlayAsset: keyboardState.exponentHighlighted ? "blue-button-highlighted" : nil), ] } private var numbersLeftItems: [KeyboardCell] { [ - .text(label: "7", foreground: .black, action: { onInsertText("7") }, enabled: model.numbersAllowed), - .text(label: "4", foreground: .black, action: { onInsertText("4") }, enabled: model.numbersAllowed), - .text(label: "1", foreground: .black, action: { onInsertText("1") }, enabled: model.numbersAllowed), - .text(label: "0", foreground: .black, action: { onInsertText("0") }, enabled: model.numbersAllowed), + .text(label: "7", foreground: .black, action: { onInsertText("7") }, enabled: keyboardState.numbersAllowed), + .text(label: "4", foreground: .black, action: { onInsertText("4") }, enabled: keyboardState.numbersAllowed), + .text(label: "1", foreground: .black, action: { onInsertText("1") }, enabled: keyboardState.numbersAllowed), + .text(label: "0", foreground: .black, action: { onInsertText("0") }, enabled: keyboardState.numbersAllowed), ] } private var numbersMiddleItems: [KeyboardCell] { [ - .text(label: "8", foreground: .black, action: { onInsertText("8") }, enabled: model.numbersAllowed), - .text(label: "5", foreground: .black, action: { onInsertText("5") }, enabled: model.numbersAllowed), - .text(label: "2", foreground: .black, action: { onInsertText("2") }, enabled: model.numbersAllowed), - .text(label: ".", foreground: .black, action: { onInsertText(".") }, enabled: model.numbersAllowed), + .text(label: "8", foreground: .black, action: { onInsertText("8") }, enabled: keyboardState.numbersAllowed), + .text(label: "5", foreground: .black, action: { onInsertText("5") }, enabled: keyboardState.numbersAllowed), + .text(label: "2", foreground: .black, action: { onInsertText("2") }, enabled: keyboardState.numbersAllowed), + .text(label: ".", foreground: .black, action: { onInsertText(".") }, enabled: keyboardState.numbersAllowed), ] } private var numbersRightItems: [KeyboardCell] { [ - .text(label: "9", foreground: .black, action: { onInsertText("9") }, enabled: model.numbersAllowed), - .text(label: "6", foreground: .black, action: { onInsertText("6") }, enabled: model.numbersAllowed), - .text(label: "3", foreground: .black, action: { onInsertText("3") }, enabled: model.numbersAllowed), + .text(label: "9", foreground: .black, action: { onInsertText("9") }, enabled: keyboardState.numbersAllowed), + .text(label: "6", foreground: .black, action: { onInsertText("6") }, enabled: keyboardState.numbersAllowed), + .text(label: "3", foreground: .black, action: { onInsertText("3") }, enabled: keyboardState.numbersAllowed), .text( - label: "=", foreground: model.equalsAllowed ? .black : Color(white: 0.67), - action: { onInsertText("=") }, enabled: model.equalsAllowed, - overlayAsset: model.equalsAllowed ? nil : "num-button-disabled"), + label: "=", foreground: keyboardState.equalsAllowed ? .black : Color(white: 0.67), + action: { onInsertText("=") }, enabled: keyboardState.equalsAllowed, + overlayAsset: keyboardState.equalsAllowed ? nil : "num-button-disabled"), ] } private var operatorItems: [KeyboardCell] { [ - .text(label: "÷", foreground: .black, action: { onInsertText("÷") }, enabled: model.operatorsAllowed), - .text(label: "×", foreground: .black, action: { onInsertText("×") }, enabled: model.operatorsAllowed), - .text(label: "-", foreground: .black, action: { onInsertText("-") }, enabled: model.operatorsAllowed), - .text(label: "+", foreground: .black, action: { onInsertText("+") }, enabled: model.operatorsAllowed), + .text(label: "÷", foreground: .black, action: { onInsertText("÷") }, enabled: keyboardState.operatorsAllowed), + .text(label: "×", foreground: .black, action: { onInsertText("×") }, enabled: keyboardState.operatorsAllowed), + .text(label: "-", foreground: .black, action: { onInsertText("-") }, enabled: keyboardState.operatorsAllowed), + .text(label: "+", foreground: .black, action: { onInsertText("+") }, enabled: keyboardState.operatorsAllowed), ] } From 63093df0d2a5d8eceb7e53a5257c396496c4017a Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:36:36 +0000 Subject: [PATCH 064/133] Replace NumbersKeyboardModel with KeyboardState in numbers view --- .../MathKeyboardSwiftUI/KeyboardState.swift | 2 +- .../NumbersKeyboardView.swift | 57 +++++-------------- 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift index d70b669..c5327fc 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift @@ -5,7 +5,7 @@ // Created by Madiyar Aitbayev on 22/03/2026. // -struct KeyboardState { +struct KeyboardState: Equatable { var currentTab: KeyboardTab = .numbers var equalsAllowed = true var fractionsAllowed = true diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 67b8a59..2f35ba0 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -2,7 +2,6 @@ import MathEditor import MathKeyboard - import Observation import SwiftUI import CoreText import Foundation @@ -26,16 +25,6 @@ } } - @Observable - final class NumbersKeyboardModel { - var numbersAllowed = true - var operatorsAllowed = true - var variablesAllowed = true - var fractionsAllowed = true - var equalsAllowed = true - var exponentHighlighted = false - } - private enum KeyboardFontRegistry { static let variableFontName: String = { guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { @@ -57,7 +46,7 @@ } final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { - private let model = NumbersKeyboardModel() + private var keyboardState = KeyboardState() private weak var editingTarget: (any UIView & UIKeyInput)? private lazy var hostingController = UIHostingController(rootView: makeRootView()) @@ -76,27 +65,27 @@ } func setNumbersState(_ enabled: Bool) { - updateModel(\.numbersAllowed, to: enabled) + updateState { $0.numbersAllowed = enabled } } func setOperatorState(_ enabled: Bool) { - updateModel(\.operatorsAllowed, to: enabled) + updateState { $0.operatorsAllowed = enabled } } func setVariablesState(_ enabled: Bool) { - updateModel(\.variablesAllowed, to: enabled) + updateState { $0.variablesAllowed = enabled } } func setFractionState(_ enabled: Bool) { - updateModel(\.fractionsAllowed, to: enabled) + updateState { $0.fractionsAllowed = enabled } } func setEqualsState(_ enabled: Bool) { - updateModel(\.equalsAllowed, to: enabled) + updateState { $0.equalsAllowed = enabled } } func setExponentState(_ highlighted: Bool) { - updateModel(\.exponentHighlighted, to: highlighted) + updateState { $0.exponentHighlighted = highlighted } } func setSquareRootState(_ highlighted: Bool) {} @@ -126,7 +115,7 @@ private func makeRootView() -> NumbersKeyboardView { NumbersKeyboardView( - model: model, + keyboardState: keyboardState, onInsertText: { [weak self] text in self?.insert(text) }, onBackspace: { [weak self] in self?.backspace() }, onDismiss: { [weak self] in self?.dismissKeyboard() } @@ -152,38 +141,22 @@ UIDevice.current.playInputClick() } - private func updateModel( - _ keyPath: ReferenceWritableKeyPath, - to value: Bool - ) { - guard model[keyPath: keyPath] != value else { return } - + private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { DispatchQueue.main.async { [weak self] in - guard let self, self.model[keyPath: keyPath] != value else { return } - self.model[keyPath: keyPath] = value + guard let self else { return } + let previousState = self.keyboardState + update(&self.keyboardState) + guard self.keyboardState != previousState else { return } + self.hostingController.rootView = self.makeRootView() } } } struct NumbersKeyboardView: View { - let model: NumbersKeyboardModel + let keyboardState: KeyboardState let onInsertText: (String) -> Void let onBackspace: () -> Void let onDismiss: () -> Void - - private var keyboardState: KeyboardState { - KeyboardState( - currentTab: .numbers, - equalsAllowed: model.equalsAllowed, - fractionsAllowed: model.fractionsAllowed, - variablesAllowed: model.variablesAllowed, - numbersAllowed: model.numbersAllowed, - operatorsAllowed: model.operatorsAllowed, - exponentHighlighted: model.exponentHighlighted, - squareRootHighlighted: false, - radicalHighlighted: false - ) - } var body: some View { GeometryReader { proxy in From 52c26f9afdb30b3e2ab77e44a5783103c7f4af3b Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:45:54 +0000 Subject: [PATCH 065/133] Restore pressed-state highlight overlays for keyboard buttons --- .../NumbersKeyboardView.swift | 82 ++++++++++++++----- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 2f35ba0..c2d33cf 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -216,7 +216,20 @@ cell.label } } - .buttonStyle(.plain) + .buttonStyle( + KeyboardPressStyle( + pressedOverlay: AnyView( + Group { + if let pressedAsset = cell.pressedAsset { + assetImage(pressedAsset) + .resizable() + } else { + Color.clear + } + } + ) + ) + ) .disabled(!cell.enabled) .opacity(cell.enabled ? 1 : 0.75) .accessibilityLabel(cell.accessibilityLabel) @@ -226,76 +239,83 @@ [ .text( label: "x", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("x") }, enabled: keyboardState.variablesAllowed), + action: { onInsertText("x") }, enabled: keyboardState.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), .text( label: "y", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("y") }, enabled: keyboardState.variablesAllowed), + action: { onInsertText("y") }, enabled: keyboardState.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), .image( imageName: "Fraction", action: { onInsertText(MTSymbolFractionSlash) }, enabled: keyboardState.fractionsAllowed, - accessibilityLabel: "Fraction"), + accessibilityLabel: "Fraction", + pressedAsset: "Keyboard-marine-pressed"), .image( imageName: "Exponent", action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent", + pressedAsset: "Keyboard-marine-pressed", overlayAsset: keyboardState.exponentHighlighted ? "blue-button-highlighted" : nil), ] } private var numbersLeftItems: [KeyboardCell] { [ - .text(label: "7", foreground: .black, action: { onInsertText("7") }, enabled: keyboardState.numbersAllowed), - .text(label: "4", foreground: .black, action: { onInsertText("4") }, enabled: keyboardState.numbersAllowed), - .text(label: "1", foreground: .black, action: { onInsertText("1") }, enabled: keyboardState.numbersAllowed), - .text(label: "0", foreground: .black, action: { onInsertText("0") }, enabled: keyboardState.numbersAllowed), + .text(label: "7", foreground: .black, action: { onInsertText("7") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "4", foreground: .black, action: { onInsertText("4") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "1", foreground: .black, action: { onInsertText("1") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "0", foreground: .black, action: { onInsertText("0") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), ] } private var numbersMiddleItems: [KeyboardCell] { [ - .text(label: "8", foreground: .black, action: { onInsertText("8") }, enabled: keyboardState.numbersAllowed), - .text(label: "5", foreground: .black, action: { onInsertText("5") }, enabled: keyboardState.numbersAllowed), - .text(label: "2", foreground: .black, action: { onInsertText("2") }, enabled: keyboardState.numbersAllowed), - .text(label: ".", foreground: .black, action: { onInsertText(".") }, enabled: keyboardState.numbersAllowed), + .text(label: "8", foreground: .black, action: { onInsertText("8") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "5", foreground: .black, action: { onInsertText("5") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "2", foreground: .black, action: { onInsertText("2") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: ".", foreground: .black, action: { onInsertText(".") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), ] } private var numbersRightItems: [KeyboardCell] { [ - .text(label: "9", foreground: .black, action: { onInsertText("9") }, enabled: keyboardState.numbersAllowed), - .text(label: "6", foreground: .black, action: { onInsertText("6") }, enabled: keyboardState.numbersAllowed), - .text(label: "3", foreground: .black, action: { onInsertText("3") }, enabled: keyboardState.numbersAllowed), + .text(label: "9", foreground: .black, action: { onInsertText("9") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "6", foreground: .black, action: { onInsertText("6") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "3", foreground: .black, action: { onInsertText("3") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( label: "=", foreground: keyboardState.equalsAllowed ? .black : Color(white: 0.67), action: { onInsertText("=") }, enabled: keyboardState.equalsAllowed, + pressedAsset: "Keyboard-grey-pressed", overlayAsset: keyboardState.equalsAllowed ? nil : "num-button-disabled"), ] } private var operatorItems: [KeyboardCell] { [ - .text(label: "÷", foreground: .black, action: { onInsertText("÷") }, enabled: keyboardState.operatorsAllowed), - .text(label: "×", foreground: .black, action: { onInsertText("×") }, enabled: keyboardState.operatorsAllowed), - .text(label: "-", foreground: .black, action: { onInsertText("-") }, enabled: keyboardState.operatorsAllowed), - .text(label: "+", foreground: .black, action: { onInsertText("+") }, enabled: keyboardState.operatorsAllowed), + .text(label: "÷", foreground: .black, action: { onInsertText("÷") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "×", foreground: .black, action: { onInsertText("×") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "-", foreground: .black, action: { onInsertText("-") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "+", foreground: .black, action: { onInsertText("+") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), ] } private var utilityBackspace: KeyboardCell { .image( imageName: "Backspace", - action: onBackspace, enabled: true, accessibilityLabel: "Backspace") + action: onBackspace, enabled: true, accessibilityLabel: "Backspace", + pressedAsset: "Keyboard-grey-pressed") } private var utilityEnter: KeyboardCell { .text( label: "Enter", foreground: .white, - action: { onInsertText("\n") }, enabled: true) + action: { onInsertText("\n") }, enabled: true, pressedAsset: "Keyboard-grey-pressed") } private var utilityDismiss: KeyboardCell { .image( imageName: "Keyboard Down", - action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard") + action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard", + pressedAsset: "Keyboard-grey-pressed") } private func assetImage(_ name: String) -> Image { @@ -317,6 +337,7 @@ let action: () -> Void let enabled: Bool let accessibilityLabel: String + let pressedAsset: String? let overlayAsset: String? static func text( @@ -327,6 +348,7 @@ action: @escaping () -> Void, enabled: Bool, accessibilityLabel: String? = nil, + pressedAsset: String? = nil, overlayAsset: String? = nil ) -> KeyboardCell { KeyboardCell( @@ -338,6 +360,7 @@ action: action, enabled: enabled, accessibilityLabel: accessibilityLabel ?? label, + pressedAsset: pressedAsset, overlayAsset: overlayAsset ) } @@ -347,6 +370,7 @@ action: @escaping () -> Void, enabled: Bool, accessibilityLabel: String, + pressedAsset: String? = nil, overlayAsset: String? = nil ) -> KeyboardCell { KeyboardCell( @@ -370,10 +394,24 @@ action: action, enabled: enabled, accessibilityLabel: accessibilityLabel, + pressedAsset: pressedAsset, overlayAsset: overlayAsset ) } } + private struct KeyboardPressStyle: ButtonStyle { + let pressedOverlay: AnyView + + func makeBody(configuration: Configuration) -> some View { + ZStack { + configuration.label + if configuration.isPressed { + pressedOverlay + } + } + } + } + #endif From fdc766ba16627db5b2cf76a88d241465f52760ea Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Mon, 23 Mar 2026 15:46:26 +0000 Subject: [PATCH 066/133] small fix --- .../Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 2f35ba0..1d78b07 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -193,7 +193,7 @@ // 2) Main four-column block (numbers left/middle/right + operators) using Grid private func mainColumnsSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { let columns = [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] - Grid(horizontalSpacing: 0, verticalSpacing: 0) { + return Grid(horizontalSpacing: 0, verticalSpacing: 0) { ForEach(0..<4, id: \.self) { row in GridRow { ForEach(0..<4, id: \.self) { column in From fbaf7a550e40f9a2ecde1f87eb463daa052d852e Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:51:47 +0000 Subject: [PATCH 067/133] Reduce pressed overlay opacity to keep key labels visible --- .../Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index c2d33cf..f09675d 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -223,6 +223,7 @@ if let pressedAsset = cell.pressedAsset { assetImage(pressedAsset) .resizable() + .opacity(0.65) } else { Color.clear } From 6ffb01b3f6702230565d4cea503d5125102b111a Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 15:55:22 +0000 Subject: [PATCH 068/133] Render pressed highlight behind key label content --- .../Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index f09675d..e0f8c4d 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -407,10 +407,10 @@ func makeBody(configuration: Configuration) -> some View { ZStack { - configuration.label if configuration.isPressed { pressedOverlay } + configuration.label } } } From 4f03d5ab3d7ed5d9ad80176d10e7a9d6bef27ba7 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 16:39:21 +0000 Subject: [PATCH 069/133] Simplify KeyboardConfigurable to apply KeyboardState --- .../KeyboardContainerView.swift | 9 +--- .../NumbersKeyboardView.swift | 48 ++++++------------- 2 files changed, 15 insertions(+), 42 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index 5190473..e289bf0 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -46,14 +46,7 @@ fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { for keyboard in keyboards.values { keyboard.setEditingTarget(editingTarget) - keyboard.setEqualsState(state.equalsAllowed) - keyboard.setFractionState(state.fractionsAllowed) - keyboard.setVariablesState(state.variablesAllowed) - keyboard.setNumbersState(state.numbersAllowed) - keyboard.setOperatorState(state.operatorsAllowed) - keyboard.setExponentState(state.exponentHighlighted) - keyboard.setSquareRootState(state.squareRootHighlighted) - keyboard.setRadicalState(state.radicalHighlighted) + keyboard.applyKeyboardState(state) } display(keyboard: keyboards[state.currentTab]!) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index e0f8c4d..270e6a1 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -9,20 +9,24 @@ protocol KeyboardConfigurable: AnyObject { func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) - func setNumbersState(_ enabled: Bool) - func setOperatorState(_ enabled: Bool) - func setVariablesState(_ enabled: Bool) - func setFractionState(_ enabled: Bool) - func setEqualsState(_ enabled: Bool) - func setExponentState(_ highlighted: Bool) - func setSquareRootState(_ highlighted: Bool) - func setRadicalState(_ highlighted: Bool) + func applyKeyboardState(_ state: KeyboardState) } extension MTKeyboard: KeyboardConfigurable { func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { self.textView = textView } + + func applyKeyboardState(_ state: KeyboardState) { + numbersAllowed = state.numbersAllowed + operatorsAllowed = state.operatorsAllowed + variablesAllowed = state.variablesAllowed + fractionsAllowed = state.fractionsAllowed + equalsAllowed = state.equalsAllowed + exponentHighlighted = state.exponentHighlighted + squareRootHighlighted = state.squareRootHighlighted + radicalHighlighted = state.radicalHighlighted + } } private enum KeyboardFontRegistry { @@ -64,34 +68,10 @@ editingTarget = textView } - func setNumbersState(_ enabled: Bool) { - updateState { $0.numbersAllowed = enabled } - } - - func setOperatorState(_ enabled: Bool) { - updateState { $0.operatorsAllowed = enabled } - } - - func setVariablesState(_ enabled: Bool) { - updateState { $0.variablesAllowed = enabled } - } - - func setFractionState(_ enabled: Bool) { - updateState { $0.fractionsAllowed = enabled } - } - - func setEqualsState(_ enabled: Bool) { - updateState { $0.equalsAllowed = enabled } + func applyKeyboardState(_ state: KeyboardState) { + updateState { $0 = state } } - func setExponentState(_ highlighted: Bool) { - updateState { $0.exponentHighlighted = highlighted } - } - - func setSquareRootState(_ highlighted: Bool) {} - - func setRadicalState(_ highlighted: Bool) {} - var enableInputClicksWhenVisible: Bool { true } private func commonInit() { From 0d65b425fcdfe9574ff293a1596442d68306cce7 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 17:13:07 +0000 Subject: [PATCH 070/133] Make KeyboardCell a pure data model without SwiftUI view storage --- .../NumbersKeyboardView.swift | 128 ++++++++++++------ 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index 270e6a1..a7914d0 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -193,7 +193,7 @@ assetImage(overlayAsset) .resizable() } - cell.label + cellContentView(cell) } } .buttonStyle( @@ -219,11 +219,11 @@ private var featureItems: [KeyboardCell] { [ .text( - label: "x", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, + label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, action: { onInsertText("x") }, enabled: keyboardState.variablesAllowed, pressedAsset: "Keyboard-marine-pressed"), .text( - label: "y", foreground: .white, fontName: KeyboardFontRegistry.variableFontName, + label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, action: { onInsertText("y") }, enabled: keyboardState.variablesAllowed, pressedAsset: "Keyboard-marine-pressed"), .image( @@ -241,29 +241,29 @@ private var numbersLeftItems: [KeyboardCell] { [ - .text(label: "7", foreground: .black, action: { onInsertText("7") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "4", foreground: .black, action: { onInsertText("4") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "1", foreground: .black, action: { onInsertText("1") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "0", foreground: .black, action: { onInsertText("0") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "7", tone: .dark, action: { onInsertText("7") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "4", tone: .dark, action: { onInsertText("4") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "1", tone: .dark, action: { onInsertText("1") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "0", tone: .dark, action: { onInsertText("0") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), ] } private var numbersMiddleItems: [KeyboardCell] { [ - .text(label: "8", foreground: .black, action: { onInsertText("8") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "5", foreground: .black, action: { onInsertText("5") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "2", foreground: .black, action: { onInsertText("2") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: ".", foreground: .black, action: { onInsertText(".") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "8", tone: .dark, action: { onInsertText("8") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "5", tone: .dark, action: { onInsertText("5") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "2", tone: .dark, action: { onInsertText("2") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: ".", tone: .dark, action: { onInsertText(".") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), ] } private var numbersRightItems: [KeyboardCell] { [ - .text(label: "9", foreground: .black, action: { onInsertText("9") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "6", foreground: .black, action: { onInsertText("6") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "3", foreground: .black, action: { onInsertText("3") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "9", tone: .dark, action: { onInsertText("9") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "6", tone: .dark, action: { onInsertText("6") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text(label: "3", tone: .dark, action: { onInsertText("3") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "=", foreground: keyboardState.equalsAllowed ? .black : Color(white: 0.67), + label: "=", tone: keyboardState.equalsAllowed ? .dark : .disabled, action: { onInsertText("=") }, enabled: keyboardState.equalsAllowed, pressedAsset: "Keyboard-grey-pressed", overlayAsset: keyboardState.equalsAllowed ? nil : "num-button-disabled"), @@ -272,10 +272,10 @@ private var operatorItems: [KeyboardCell] { [ - .text(label: "÷", foreground: .black, action: { onInsertText("÷") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "×", foreground: .black, action: { onInsertText("×") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "-", foreground: .black, action: { onInsertText("-") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "+", foreground: .black, action: { onInsertText("+") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "÷", tone: .dark, action: { onInsertText("÷") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "×", tone: .dark, action: { onInsertText("×") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "-", tone: .dark, action: { onInsertText("-") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "+", tone: .dark, action: { onInsertText("+") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), ] } @@ -288,7 +288,7 @@ private var utilityEnter: KeyboardCell { .text( - label: "Enter", foreground: .white, + label: "Enter", tone: .light, action: { onInsertText("\n") }, enabled: true, pressedAsset: "Keyboard-grey-pressed") } @@ -309,12 +309,67 @@ } return Image(systemName: "questionmark.square.dashed") } + + @ViewBuilder + private func cellContentView(_ cell: KeyboardCell) -> some View { + switch cell.content { + case .text(let text): + Text(text.value) + .font(.custom(text.fontName, size: text.fontSize)) + .foregroundColor(textColor(for: text.tone)) + case .image(let image): + if let uiImage = UIImage( + named: image.name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) { + Image(uiImage: uiImage) + .renderingMode(.original) + .resizable() + .scaledToFit() + .padding(image.padding) + } else { + Image(systemName: "questionmark.square.dashed") + } + } + } + + private func textColor(for tone: KeyboardCell.TextTone) -> Color { + switch tone { + case .light: .white + case .dark: .black + case .disabled: Color(white: 0.67) + } + } } private struct KeyboardCell: Identifiable { + enum Content { + case text(TextContent) + case image(ImageContent) + } + + struct TextContent { + let value: String + let fontName: String + let fontSize: CGFloat + let tone: TextTone + } + + struct ImageContent { + let name: String + let padding: CGFloat + } + + enum TextTone { + case light + case dark + case disabled + } + let id = UUID() - let label: AnyView + let content: Content let action: () -> Void let enabled: Bool let accessibilityLabel: String @@ -323,7 +378,7 @@ static func text( label: String, - foreground: Color, + tone: TextTone, fontName: String = "HelveticaNeue-Thin", fontSize: CGFloat = 20, action: @escaping () -> Void, @@ -333,10 +388,13 @@ overlayAsset: String? = nil ) -> KeyboardCell { KeyboardCell( - label: AnyView( - Text(label) - .font(.custom(fontName, size: fontSize)) - .foregroundColor(foreground) + content: .text( + TextContent( + value: label, + fontName: fontName, + fontSize: fontSize, + tone: tone + ) ), action: action, enabled: enabled, @@ -355,23 +413,7 @@ overlayAsset: String? = nil ) -> KeyboardCell { KeyboardCell( - label: AnyView( - Group { - if let uiImage = UIImage( - named: imageName, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) { - Image(uiImage: uiImage) - .renderingMode(.original) - .resizable() - .scaledToFit() - .padding(8) - } else { - Image(systemName: "questionmark.square.dashed") - } - } - ), + content: .image(ImageContent(name: imageName, padding: 8)), action: action, enabled: enabled, accessibilityLabel: accessibilityLabel, From 1acf19a776293223526a8132b578809c6ddb0c73 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 17:15:27 +0000 Subject: [PATCH 071/133] Fix MTKeyboard state application to use existing setter methods --- .../NumbersKeyboardView.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift index a7914d0..4d98528 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift @@ -18,14 +18,14 @@ } func applyKeyboardState(_ state: KeyboardState) { - numbersAllowed = state.numbersAllowed - operatorsAllowed = state.operatorsAllowed - variablesAllowed = state.variablesAllowed - fractionsAllowed = state.fractionsAllowed - equalsAllowed = state.equalsAllowed - exponentHighlighted = state.exponentHighlighted - squareRootHighlighted = state.squareRootHighlighted - radicalHighlighted = state.radicalHighlighted + setNumbersState(state.numbersAllowed) + setOperatorState(state.operatorsAllowed) + setVariablesState(state.variablesAllowed) + setFractionState(state.fractionsAllowed) + setEqualsState(state.equalsAllowed) + setExponentState(state.exponentHighlighted) + setSquareRootState(state.squareRootHighlighted) + setRadicalState(state.radicalHighlighted) } } From 67a8bcae2a3df099a31a2a8f5debd7f4f5a9723f Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Mon, 23 Mar 2026 20:36:46 +0000 Subject: [PATCH 072/133] Refactor --- .../project.pbxproj | 8 +- MathKeyboardSwiftUI/Package.swift | 4 +- .../Keyboard/KeyButton.swift | 73 +++ .../Keyboard/KeyboardCell.swift | 87 ++++ .../Keyboard/MTMathKeyboardCompat.swift | 55 +++ .../Keyboard/NumbersKeyboardUIView.swift | 119 +++++ .../Keyboard/NumbersKeyboardView.swift | 170 +++++++ .../MathKeyboardRootView.swift | 2 +- .../NumbersKeyboardView.swift | 440 ------------------ 9 files changed, 511 insertions(+), 447 deletions(-) create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyButton.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardCell.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MTMathKeyboardCompat.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift delete mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj index d9ce350..dee3e76 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj @@ -290,10 +290,10 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 11; + MACOSX_DEPLOYMENT_TARGET = 13.5; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = matheditor.MathEditorSwiftUIExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -329,10 +329,10 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 11; + MACOSX_DEPLOYMENT_TARGET = 13.5; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = matheditor.MathEditorSwiftUIExample; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/MathKeyboardSwiftUI/Package.swift b/MathKeyboardSwiftUI/Package.swift index de036e3..a073ecc 100644 --- a/MathKeyboardSwiftUI/Package.swift +++ b/MathKeyboardSwiftUI/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "MathKeyboardSwiftUI", defaultLocalization: "en", - platforms: [.iOS(.v17), .macOS(.v11)], + platforms: [.iOS(.v17), .macOS(.v13)], products: [ .library( name: "MathKeyboardSwiftUI", @@ -19,7 +19,7 @@ let package = Package( .target( name: "MathKeyboardSwiftUI", dependencies: [ - .product(name: "MathKeyboard", package: "MathEditor"), + .product(name: "MathKeyboard", package: "MathEditor", condition: .when(platforms: [.iOS])), .product(name: "MathEditor", package: "MathEditor"), ], path: "Sources/MathKeyboardSwiftUI" diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyButton.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyButton.swift new file mode 100644 index 0000000..998e2c2 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyButton.swift @@ -0,0 +1,73 @@ +// +// KeyButton.swift +// MathKeyboardSwiftUI +// +// Created by Madiyar Aitbayev on 23/03/2026. +// + +import SwiftUI + +struct KeyButton: View { + var cell: KeyboardCell + + init(_ cell: KeyboardCell) { + self.cell = cell + } + + var body: some View { + Button(action: cell.action) { + ZStack { + Rectangle().fill(Color.white.opacity(0.001)) + if let overlayAsset = cell.overlayAsset { + mtMathImage(overlayAsset) + .resizable() + } + switch cell.content { + case .text(let text): + Text(text.value) + .font(.custom(text.fontName, size: text.fontSize)) + .foregroundColor(textColor(for: text.tone)) + case .image(let image): + mtMathImage(image.name) + .renderingMode(.original) + .resizable() + .scaledToFit() + .padding(image.padding) + } + } + } + .buttonStyle( + KeyboardPressStyle(pressedAsset: cell.pressedAsset) + ) + .disabled(!cell.enabled) + .opacity(cell.enabled ? 1 : 0.75) + .accessibilityLabel(cell.accessibilityLabel) + } +} + +private func textColor(for tone: KeyboardCell.TextTone) -> Color { + switch tone { + case .light: .white + case .dark: .black + case .disabled: Color(white: 0.67) + } +} + +private struct KeyboardPressStyle: ButtonStyle { + let pressedAsset: String? + + func makeBody(configuration: Configuration) -> some View { + ZStack { + if configuration.isPressed { + if let pressedAsset { + mtMathImage(pressedAsset) + .resizable() + .opacity(1.0) + } else { + Color.clear + } + } + configuration.label + } + } +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardCell.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardCell.swift new file mode 100644 index 0000000..b85af21 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardCell.swift @@ -0,0 +1,87 @@ +// +// KeyboardCell.swift +// MathKeyboardSwiftUI +// +// Created by Madiyar Aitbayev on 23/03/2026. +// + +import Foundation + +struct KeyboardCell: Identifiable { + enum Content { + case text(TextContent) + case image(ImageContent) + } + + struct TextContent { + let value: String + let fontName: String + let fontSize: Double + let tone: TextTone + } + + struct ImageContent { + let name: String + let padding: Double + } + + enum TextTone { + case light + case dark + case disabled + } + + let id = UUID() + let content: Content + let action: () -> Void + let enabled: Bool + let accessibilityLabel: String + let pressedAsset: String? + let overlayAsset: String? + + static func text( + label: String, + tone: TextTone, + fontName: String = "HelveticaNeue-Thin", + fontSize: CGFloat = 20, + action: @escaping () -> Void, + enabled: Bool, + accessibilityLabel: String? = nil, + pressedAsset: String? = nil, + overlayAsset: String? = nil + ) -> KeyboardCell { + KeyboardCell( + content: .text( + TextContent( + value: label, + fontName: fontName, + fontSize: fontSize, + tone: tone + ) + ), + action: action, + enabled: enabled, + accessibilityLabel: accessibilityLabel ?? label, + pressedAsset: pressedAsset, + overlayAsset: overlayAsset + ) + } + + static func image( + imageName: String, + action: @escaping () -> Void, + enabled: Bool, + accessibilityLabel: String, + pressedAsset: String? = nil, + overlayAsset: String? = nil + ) -> KeyboardCell { + KeyboardCell( + content: .image(ImageContent(name: imageName, padding: 8)), + action: action, + enabled: enabled, + accessibilityLabel: accessibilityLabel, + pressedAsset: pressedAsset, + overlayAsset: overlayAsset + ) + } +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MTMathKeyboardCompat.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MTMathKeyboardCompat.swift new file mode 100644 index 0000000..7f19926 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MTMathKeyboardCompat.swift @@ -0,0 +1,55 @@ +// +// MTMathKeyboard.swift +// MathKeyboardSwiftUI +// +// Created by Madiyar Aitbayev on 23/03/2026. +// + +import SwiftUI + +#if os(iOS) + + import MathKeyboard + + func mtMathImage(_ name: String) -> Image { + if let image = UIImage( + named: name, + in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + compatibleWith: nil + ) { + return Image(uiImage: image) + } + return Image(systemName: "questionmark.square.dashed") + } + +#else + + func mtMathImage(_ name: String) -> Image { + Image(systemName: "questionmark.square.dashed") + } + +#endif + +enum KeyboardFontRegistry { + static let variableFontName: String = { + #if os(iOS) + guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { + return "HelveticaNeue" + } + guard + let fontURL = bundle.url(forResource: "lmroman10-bolditalic", withExtension: "otf"), + let provider = CGDataProvider(url: fontURL as CFURL), + let font = CGFont(provider) + else { + return "HelveticaNeue" + } + + let postScriptName = font.postScriptName as String? ?? "HelveticaNeue" + var error: Unmanaged? + CTFontManagerRegisterGraphicsFont(font, &error) + return postScriptName + #else + return "HelveticaNeue" + #endif + }() +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift new file mode 100644 index 0000000..fb80f84 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift @@ -0,0 +1,119 @@ +// +// NumbersKeyboardUIView.swift +// MathKeyboardSwiftUI +// +// Created by Madiyar Aitbayev on 23/03/2026. +// + +#if os(iOS) + + import UIKit + import MathKeyboard + import SwiftUI + + protocol KeyboardConfigurable: AnyObject { + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) + func applyKeyboardState(_ state: KeyboardState) + } + + extension MTKeyboard: KeyboardConfigurable { + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + self.textView = textView + } + + func applyKeyboardState(_ state: KeyboardState) { + setNumbersState(state.numbersAllowed) + setOperatorState(state.operatorsAllowed) + setVariablesState(state.variablesAllowed) + setFractionState(state.fractionsAllowed) + setEqualsState(state.equalsAllowed) + setExponentState(state.exponentHighlighted) + setSquareRootState(state.squareRootHighlighted) + setRadicalState(state.radicalHighlighted) + } + } + + final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { + private var keyboardState = KeyboardState() + private weak var editingTarget: (any UIView & UIKeyInput)? + private lazy var hostingController = UIHostingController(rootView: makeRootView()) + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + editingTarget = textView + } + + func applyKeyboardState(_ state: KeyboardState) { + updateState { $0 = state } + } + + var enableInputClicksWhenVisible: Bool { true } + + private func commonInit() { + backgroundColor = .white + + let hostedView = hostingController.view! + if #available(iOS 16.4, *) { + hostingController.safeAreaRegions = [] + } + hostedView.backgroundColor = .clear + hostedView.translatesAutoresizingMaskIntoConstraints = false + addSubview(hostedView) + + NSLayoutConstraint.activate([ + hostedView.topAnchor.constraint(equalTo: topAnchor), + hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), + hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), + hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + private func makeRootView() -> NumbersKeyboardView { + NumbersKeyboardView( + keyboardState: keyboardState, + onInsertText: { [weak self] text in self?.insert(text) }, + onBackspace: { [weak self] in self?.backspace() }, + onDismiss: { [weak self] in self?.dismissKeyboard() } + ) + } + + private func insert(_ text: String) { + playClickForCustomKeyTap() + editingTarget?.insertText(text) + } + + private func backspace() { + playClickForCustomKeyTap() + editingTarget?.deleteBackward() + } + + private func dismissKeyboard() { + playClickForCustomKeyTap() + editingTarget?.resignFirstResponder() + } + + private func playClickForCustomKeyTap() { + UIDevice.current.playInputClick() + } + + private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + let previousState = self.keyboardState + update(&self.keyboardState) + guard self.keyboardState != previousState else { return } + self.hostingController.rootView = self.makeRootView() + } + } + } + +#endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift new file mode 100644 index 0000000..8601f04 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift @@ -0,0 +1,170 @@ +import CoreText +import Foundation +import MathEditor +import SwiftUI + +struct NumbersKeyboardView: View { + let keyboardState: KeyboardState + let onInsertText: (String) -> Void + let onBackspace: () -> Void + let onDismiss: () -> Void + + var body: some View { + GeometryReader { proxy in + let totalWidth = proxy.size.width + let totalHeight = proxy.size.height + let utilityWidth = totalWidth * 0.225 + let standardColumnWidth = (totalWidth - utilityWidth) / 5 + let rowHeight = totalHeight / 4 + + ZStack { + mtMathImage("Numbers Keyboard") + .resizable() + .frame(width: totalWidth, height: totalHeight) + + HStack(spacing: 0) { + VStack(spacing: 0) { + ForEach(featureItems) { item in + KeyButton(item).frame(width: standardColumnWidth, height: rowHeight) + } + } + mainColumnsSection(columnWidth: standardColumnWidth, rowHeight: rowHeight) + VStack(spacing: 0) { + KeyButton(utilityBackspace).frame(width: utilityWidth, height: rowHeight) + KeyButton(utilityEnter).frame(width: utilityWidth, height: rowHeight * 2) + KeyButton(utilityDismiss).frame(width: utilityWidth, height: rowHeight) + } + } + } + .frame(width: totalWidth, height: totalHeight) + } + } + + // 2) Main four-column block (numbers left/middle/right + operators) using Grid + private func mainColumnsSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { + let columns = [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] + return Grid(horizontalSpacing: 0, verticalSpacing: 0) { + ForEach(0..<4, id: \.self) { row in + GridRow { + ForEach(0..<4, id: \.self) { column in + KeyButton(columns[column][row]) + .frame(width: columnWidth, height: rowHeight) + } + } + } + } + } + + private var featureItems: [KeyboardCell] { + [ + .text( + label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, + action: { onInsertText("x") }, enabled: keyboardState.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), + .text( + label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, + action: { onInsertText("y") }, enabled: keyboardState.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), + .image( + imageName: "Fraction", + action: { onInsertText(MTSymbolFractionSlash) }, enabled: keyboardState.fractionsAllowed, + accessibilityLabel: "Fraction", + pressedAsset: "Keyboard-marine-pressed"), + .image( + imageName: "Exponent", + action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent", + pressedAsset: "Keyboard-marine-pressed", + overlayAsset: keyboardState.exponentHighlighted ? "blue-button-highlighted" : nil), + ] + } + + private var numbersLeftItems: [KeyboardCell] { + [ + .text( + label: "7", tone: .dark, action: { onInsertText("7") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "4", tone: .dark, action: { onInsertText("4") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "1", tone: .dark, action: { onInsertText("1") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "0", tone: .dark, action: { onInsertText("0") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + ] + } + + private var numbersMiddleItems: [KeyboardCell] { + [ + .text( + label: "8", tone: .dark, action: { onInsertText("8") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "5", tone: .dark, action: { onInsertText("5") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "2", tone: .dark, action: { onInsertText("2") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: ".", tone: .dark, action: { onInsertText(".") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + ] + } + + private var numbersRightItems: [KeyboardCell] { + [ + .text( + label: "9", tone: .dark, action: { onInsertText("9") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "6", tone: .dark, action: { onInsertText("6") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "3", tone: .dark, action: { onInsertText("3") }, + enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "=", tone: keyboardState.equalsAllowed ? .dark : .disabled, + action: { onInsertText("=") }, enabled: keyboardState.equalsAllowed, + pressedAsset: "Keyboard-grey-pressed", + overlayAsset: keyboardState.equalsAllowed ? nil : "num-button-disabled"), + ] + } + + private var operatorItems: [KeyboardCell] { + [ + .text( + label: "÷", tone: .dark, action: { onInsertText("÷") }, + enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "×", tone: .dark, action: { onInsertText("×") }, + enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "-", tone: .dark, action: { onInsertText("-") }, + enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "+", tone: .dark, action: { onInsertText("+") }, + enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + ] + } + + private var utilityBackspace: KeyboardCell { + .image( + imageName: "Backspace", + action: onBackspace, enabled: true, accessibilityLabel: "Backspace", + pressedAsset: "Keyboard-grey-pressed") + } + + private var utilityEnter: KeyboardCell { + .text( + label: "Enter", tone: .light, + action: { onInsertText("\n") }, enabled: true, pressedAsset: "Keyboard-grey-pressed") + } + + private var utilityDismiss: KeyboardCell { + .image( + imageName: "Keyboard Down", + action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard", + pressedAsset: "Keyboard-grey-pressed") + } +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift index e657f84..318a907 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -5,10 +5,10 @@ // Created by Madiyar Aitbayev on 22/03/2026. // -import MathKeyboard import SwiftUI #if os(iOS) + import MathKeyboard public struct MathKeyboardRootView: View { let state: KeyboardState diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift deleted file mode 100644 index 023f11a..0000000 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/NumbersKeyboardView.swift +++ /dev/null @@ -1,440 +0,0 @@ -#if os(iOS) - - import MathEditor - import MathKeyboard - import SwiftUI - import CoreText - import Foundation - import UIKit - - protocol KeyboardConfigurable: AnyObject { - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) - func applyKeyboardState(_ state: KeyboardState) - } - - extension MTKeyboard: KeyboardConfigurable { - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { - self.textView = textView - } - - func applyKeyboardState(_ state: KeyboardState) { - setNumbersState(state.numbersAllowed) - setOperatorState(state.operatorsAllowed) - setVariablesState(state.variablesAllowed) - setFractionState(state.fractionsAllowed) - setEqualsState(state.equalsAllowed) - setExponentState(state.exponentHighlighted) - setSquareRootState(state.squareRootHighlighted) - setRadicalState(state.radicalHighlighted) - } - } - - private enum KeyboardFontRegistry { - static let variableFontName: String = { - guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { - return "HelveticaNeue" - } - guard - let fontURL = bundle.url(forResource: "lmroman10-bolditalic", withExtension: "otf"), - let provider = CGDataProvider(url: fontURL as CFURL), - let font = CGFont(provider) - else { - return "HelveticaNeue" - } - - let postScriptName = font.postScriptName as String? ?? "HelveticaNeue" - var error: Unmanaged? - CTFontManagerRegisterGraphicsFont(font, &error) - return postScriptName - }() - } - - final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { - private var keyboardState = KeyboardState() - private weak var editingTarget: (any UIView & UIKeyInput)? - private lazy var hostingController = UIHostingController(rootView: makeRootView()) - - override init(frame: CGRect) { - super.init(frame: frame) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { - editingTarget = textView - } - - func applyKeyboardState(_ state: KeyboardState) { - updateState { $0 = state } - } - - var enableInputClicksWhenVisible: Bool { true } - - private func commonInit() { - backgroundColor = .white - - let hostedView = hostingController.view! - if #available(iOS 16.4, *) { - hostingController.safeAreaRegions = [] - } - hostedView.backgroundColor = .clear - hostedView.translatesAutoresizingMaskIntoConstraints = false - addSubview(hostedView) - - NSLayoutConstraint.activate([ - hostedView.topAnchor.constraint(equalTo: topAnchor), - hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), - hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), - hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - } - - private func makeRootView() -> NumbersKeyboardView { - NumbersKeyboardView( - keyboardState: keyboardState, - onInsertText: { [weak self] text in self?.insert(text) }, - onBackspace: { [weak self] in self?.backspace() }, - onDismiss: { [weak self] in self?.dismissKeyboard() } - ) - } - - private func insert(_ text: String) { - playClickForCustomKeyTap() - editingTarget?.insertText(text) - } - - private func backspace() { - playClickForCustomKeyTap() - editingTarget?.deleteBackward() - } - - private func dismissKeyboard() { - playClickForCustomKeyTap() - editingTarget?.resignFirstResponder() - } - - private func playClickForCustomKeyTap() { - UIDevice.current.playInputClick() - } - - private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { - DispatchQueue.main.async { [weak self] in - guard let self else { return } - let previousState = self.keyboardState - update(&self.keyboardState) - guard self.keyboardState != previousState else { return } - self.hostingController.rootView = self.makeRootView() - } - } - } - - struct NumbersKeyboardView: View { - let keyboardState: KeyboardState - let onInsertText: (String) -> Void - let onBackspace: () -> Void - let onDismiss: () -> Void - - var body: some View { - GeometryReader { proxy in - let totalWidth = proxy.size.width - let totalHeight = proxy.size.height - let utilityWidth = totalWidth * 0.225 - let standardColumnWidth = (totalWidth - utilityWidth) / 5 - let rowHeight = totalHeight / 4 - - ZStack { - assetImage("Numbers Keyboard") - .resizable() - .frame(width: totalWidth, height: totalHeight) - - HStack(spacing: 0) { - VStack(spacing: 0) { - keyButton(featureItems[0]).frame(width: standardColumnWidth, height: rowHeight) - keyButton(featureItems[1]).frame(width: standardColumnWidth, height: rowHeight) - keyButton(featureItems[2]).frame(width: standardColumnWidth, height: rowHeight) - keyButton(featureItems[3]).frame(width: standardColumnWidth, height: rowHeight) - } - mainColumnsSection(columnWidth: standardColumnWidth, rowHeight: rowHeight) - VStack(spacing: 0) { - keyButton(utilityBackspace).frame(width: utilityWidth, height: rowHeight) - keyButton(utilityEnter).frame(width: utilityWidth, height: rowHeight * 2) - keyButton(utilityDismiss).frame(width: utilityWidth, height: rowHeight) - } - } - } - .frame(width: totalWidth, height: totalHeight) - } - } - - // 2) Main four-column block (numbers left/middle/right + operators) using Grid - private func mainColumnsSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { - let columns = [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] - return Grid(horizontalSpacing: 0, verticalSpacing: 0) { - ForEach(0..<4, id: \.self) { row in - GridRow { - ForEach(0..<4, id: \.self) { column in - keyButton(columns[column][row]) - .frame(width: columnWidth, height: rowHeight) - } - } - } - } - } - - private func keyButton(_ cell: KeyboardCell) -> some View { - Button(action: cell.action) { - ZStack { - Rectangle().fill(Color.white.opacity(0.001)) - if let overlayAsset = cell.overlayAsset { - assetImage(overlayAsset) - .resizable() - } - cellContentView(cell) - } - } - .buttonStyle( - KeyboardPressStyle( - pressedOverlay: AnyView( - Group { - if let pressedAsset = cell.pressedAsset { - assetImage(pressedAsset) - .resizable() - .opacity(0.65) - } else { - Color.clear - } - } - ) - ) - ) - .disabled(!cell.enabled) - .opacity(cell.enabled ? 1 : 0.75) - .accessibilityLabel(cell.accessibilityLabel) - } - - private var featureItems: [KeyboardCell] { - [ - .text( - label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("x") }, enabled: keyboardState.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), - .text( - label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("y") }, enabled: keyboardState.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), - .image( - imageName: "Fraction", - action: { onInsertText(MTSymbolFractionSlash) }, enabled: keyboardState.fractionsAllowed, - accessibilityLabel: "Fraction", - pressedAsset: "Keyboard-marine-pressed"), - .image( - imageName: "Exponent", - action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent", - pressedAsset: "Keyboard-marine-pressed", - overlayAsset: keyboardState.exponentHighlighted ? "blue-button-highlighted" : nil), - ] - } - - private var numbersLeftItems: [KeyboardCell] { - [ - .text(label: "7", tone: .dark, action: { onInsertText("7") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "4", tone: .dark, action: { onInsertText("4") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "1", tone: .dark, action: { onInsertText("1") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "0", tone: .dark, action: { onInsertText("0") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - ] - } - - private var numbersMiddleItems: [KeyboardCell] { - [ - .text(label: "8", tone: .dark, action: { onInsertText("8") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "5", tone: .dark, action: { onInsertText("5") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "2", tone: .dark, action: { onInsertText("2") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: ".", tone: .dark, action: { onInsertText(".") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - ] - } - - private var numbersRightItems: [KeyboardCell] { - [ - .text(label: "9", tone: .dark, action: { onInsertText("9") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "6", tone: .dark, action: { onInsertText("6") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text(label: "3", tone: .dark, action: { onInsertText("3") }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "=", tone: keyboardState.equalsAllowed ? .dark : .disabled, - action: { onInsertText("=") }, enabled: keyboardState.equalsAllowed, - pressedAsset: "Keyboard-grey-pressed", - overlayAsset: keyboardState.equalsAllowed ? nil : "num-button-disabled"), - ] - } - - private var operatorItems: [KeyboardCell] { - [ - .text(label: "÷", tone: .dark, action: { onInsertText("÷") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "×", tone: .dark, action: { onInsertText("×") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "-", tone: .dark, action: { onInsertText("-") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "+", tone: .dark, action: { onInsertText("+") }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - ] - } - - private var utilityBackspace: KeyboardCell { - .image( - imageName: "Backspace", - action: onBackspace, enabled: true, accessibilityLabel: "Backspace", - pressedAsset: "Keyboard-grey-pressed") - } - - private var utilityEnter: KeyboardCell { - .text( - label: "Enter", tone: .light, - action: { onInsertText("\n") }, enabled: true, pressedAsset: "Keyboard-grey-pressed") - } - - private var utilityDismiss: KeyboardCell { - .image( - imageName: "Keyboard Down", - action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard", - pressedAsset: "Keyboard-grey-pressed") - } - - private func assetImage(_ name: String) -> Image { - if let image = UIImage( - named: name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) { - return Image(uiImage: image) - } - return Image(systemName: "questionmark.square.dashed") - } - - @ViewBuilder - private func cellContentView(_ cell: KeyboardCell) -> some View { - switch cell.content { - case .text(let text): - Text(text.value) - .font(.custom(text.fontName, size: text.fontSize)) - .foregroundColor(textColor(for: text.tone)) - case .image(let image): - if let uiImage = UIImage( - named: image.name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) { - Image(uiImage: uiImage) - .renderingMode(.original) - .resizable() - .scaledToFit() - .padding(image.padding) - } else { - Image(systemName: "questionmark.square.dashed") - } - } - } - - private func textColor(for tone: KeyboardCell.TextTone) -> Color { - switch tone { - case .light: .white - case .dark: .black - case .disabled: Color(white: 0.67) - } - } - - } - - private struct KeyboardCell: Identifiable { - enum Content { - case text(TextContent) - case image(ImageContent) - } - - struct TextContent { - let value: String - let fontName: String - let fontSize: CGFloat - let tone: TextTone - } - - struct ImageContent { - let name: String - let padding: CGFloat - } - - enum TextTone { - case light - case dark - case disabled - } - - let id = UUID() - let content: Content - let action: () -> Void - let enabled: Bool - let accessibilityLabel: String - let pressedAsset: String? - let overlayAsset: String? - - static func text( - label: String, - tone: TextTone, - fontName: String = "HelveticaNeue-Thin", - fontSize: CGFloat = 20, - action: @escaping () -> Void, - enabled: Bool, - accessibilityLabel: String? = nil, - pressedAsset: String? = nil, - overlayAsset: String? = nil - ) -> KeyboardCell { - KeyboardCell( - content: .text( - TextContent( - value: label, - fontName: fontName, - fontSize: fontSize, - tone: tone - ) - ), - action: action, - enabled: enabled, - accessibilityLabel: accessibilityLabel ?? label, - pressedAsset: pressedAsset, - overlayAsset: overlayAsset - ) - } - - static func image( - imageName: String, - action: @escaping () -> Void, - enabled: Bool, - accessibilityLabel: String, - pressedAsset: String? = nil, - overlayAsset: String? = nil - ) -> KeyboardCell { - KeyboardCell( - content: .image(ImageContent(name: imageName, padding: 8)), - action: action, - enabled: enabled, - accessibilityLabel: accessibilityLabel, - pressedAsset: pressedAsset, - overlayAsset: overlayAsset - ) - } - - } - - private struct KeyboardPressStyle: ButtonStyle { - let pressedOverlay: AnyView - - func makeBody(configuration: Configuration) -> some View { - ZStack { - if configuration.isPressed { - pressedOverlay - } - configuration.label - } - } - } - -#endif From 28f01273aaef51dc2fdff81518c60f1916028a47 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Mon, 23 Mar 2026 23:55:28 +0000 Subject: [PATCH 073/133] Simplify shared keyboard composition --- .../Keyboard/KeyboardAction.swift | 5 + .../Keyboard/KeyboardViewComponents.swift | 104 ++++++++++++++ .../Keyboard/NumbersKeyboardUIView.swift | 86 +---------- .../Keyboard/NumbersKeyboardView.swift | 134 ++++-------------- .../Keyboard/OperationsKeyboardView.swift | 58 ++++++++ .../Keyboard/SwiftUIKeyboardHostView.swift | 88 ++++++++++++ .../KeyboardContainerView.swift | 22 ++- 7 files changed, 303 insertions(+), 194 deletions(-) create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardAction.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/SwiftUIKeyboardHostView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardAction.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardAction.swift new file mode 100644 index 0000000..fe2ea70 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardAction.swift @@ -0,0 +1,5 @@ +enum KeyboardAction { + case insertText(String) + case backspace + case dismiss +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift new file mode 100644 index 0000000..5329574 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift @@ -0,0 +1,104 @@ +import Foundation +import MathEditor +import SwiftUI + +struct KeyboardBaseLayoutView: View { + let backgroundImageName: String + let featureItems: [KeyboardCell] + let middleColumns: [[KeyboardCell]] + let utilityBackspace: KeyboardCell + let utilityEnter: KeyboardCell + let utilityDismiss: KeyboardCell + + var body: some View { + GeometryReader { proxy in + let totalWidth = proxy.size.width + let totalHeight = proxy.size.height + let utilityWidth = totalWidth * 0.225 + let standardColumnWidth = (totalWidth - utilityWidth) / 5 + let rowHeight = totalHeight / 4 + + ZStack { + mtMathImage(backgroundImageName) + .resizable() + .frame(width: totalWidth, height: totalHeight) + + HStack(spacing: 0) { + VStack(spacing: 0) { + ForEach(featureItems) { item in + KeyButton(item).frame(width: standardColumnWidth, height: rowHeight) + } + } + + Grid(horizontalSpacing: 0, verticalSpacing: 0) { + ForEach(0..<4, id: \.self) { row in + GridRow { + ForEach(0..<4, id: \.self) { column in + KeyButton(middleColumns[column][row]) + .frame(width: standardColumnWidth, height: rowHeight) + } + } + } + } + + VStack(spacing: 0) { + KeyButton(utilityBackspace).frame(width: utilityWidth, height: rowHeight) + KeyButton(utilityEnter).frame(width: utilityWidth, height: rowHeight * 2) + KeyButton(utilityDismiss).frame(width: utilityWidth, height: rowHeight) + } + } + } + .frame(width: totalWidth, height: totalHeight) + } + } +} + +func commonFeatureItems( + keyboardState: KeyboardState, + onAction: @escaping (KeyboardAction) -> Void +) -> [KeyboardCell] { + [ + .text( + label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, + action: { onAction(.insertText("x")) }, enabled: keyboardState.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), + .text( + label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, + action: { onAction(.insertText("y")) }, enabled: keyboardState.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), + .image( + imageName: "Fraction", + action: { onAction(.insertText(MTSymbolFractionSlash)) }, + enabled: keyboardState.fractionsAllowed, + accessibilityLabel: "Fraction", + pressedAsset: "Keyboard-marine-pressed"), + .image( + imageName: "Exponent", + action: { onAction(.insertText("^")) }, enabled: true, accessibilityLabel: "Exponent", + pressedAsset: "Keyboard-marine-pressed", + overlayAsset: keyboardState.exponentHighlighted ? "blue-button-highlighted" : nil), + ] +} + +struct KeyboardUtilityItems { + let backspace: KeyboardCell + let enter: KeyboardCell + let dismiss: KeyboardCell +} + +func commonUtilityItems(onAction: @escaping (KeyboardAction) -> Void) -> KeyboardUtilityItems { + KeyboardUtilityItems( + backspace: .image( + imageName: "Backspace", + action: { onAction(.backspace) }, enabled: true, accessibilityLabel: "Backspace", + pressedAsset: "Keyboard-grey-pressed"), + enter: .text( + label: "Enter", tone: .light, + action: { onAction(.insertText("\n")) }, enabled: true, + pressedAsset: "Keyboard-grey-pressed"), + dismiss: .image( + imageName: "Keyboard Down", + action: { onAction(.dismiss) }, enabled: true, accessibilityLabel: "Dismiss keyboard", + pressedAsset: "Keyboard-grey-pressed") + ) +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift index fb80f84..59cca76 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift @@ -7,9 +7,8 @@ #if os(iOS) - import UIKit import MathKeyboard - import SwiftUI + import UIKit protocol KeyboardConfigurable: AnyObject { func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) @@ -33,87 +32,4 @@ } } - final class NumbersKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { - private var keyboardState = KeyboardState() - private weak var editingTarget: (any UIView & UIKeyInput)? - private lazy var hostingController = UIHostingController(rootView: makeRootView()) - - override init(frame: CGRect) { - super.init(frame: frame) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { - editingTarget = textView - } - - func applyKeyboardState(_ state: KeyboardState) { - updateState { $0 = state } - } - - var enableInputClicksWhenVisible: Bool { true } - - private func commonInit() { - backgroundColor = .white - - let hostedView = hostingController.view! - if #available(iOS 16.4, *) { - hostingController.safeAreaRegions = [] - } - hostedView.backgroundColor = .clear - hostedView.translatesAutoresizingMaskIntoConstraints = false - addSubview(hostedView) - - NSLayoutConstraint.activate([ - hostedView.topAnchor.constraint(equalTo: topAnchor), - hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), - hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), - hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - } - - private func makeRootView() -> NumbersKeyboardView { - NumbersKeyboardView( - keyboardState: keyboardState, - onInsertText: { [weak self] text in self?.insert(text) }, - onBackspace: { [weak self] in self?.backspace() }, - onDismiss: { [weak self] in self?.dismissKeyboard() } - ) - } - - private func insert(_ text: String) { - playClickForCustomKeyTap() - editingTarget?.insertText(text) - } - - private func backspace() { - playClickForCustomKeyTap() - editingTarget?.deleteBackward() - } - - private func dismissKeyboard() { - playClickForCustomKeyTap() - editingTarget?.resignFirstResponder() - } - - private func playClickForCustomKeyTap() { - UIDevice.current.playInputClick() - } - - private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { - DispatchQueue.main.async { [weak self] in - guard let self else { return } - let previousState = self.keyboardState - update(&self.keyboardState) - guard self.keyboardState != previousState else { return } - self.hostingController.rootView = self.makeRootView() - } - } - } - #endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift index 8601f04..ea11051 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift @@ -1,96 +1,38 @@ -import CoreText import Foundation -import MathEditor import SwiftUI struct NumbersKeyboardView: View { let keyboardState: KeyboardState - let onInsertText: (String) -> Void - let onBackspace: () -> Void - let onDismiss: () -> Void + let onAction: (KeyboardAction) -> Void - var body: some View { - GeometryReader { proxy in - let totalWidth = proxy.size.width - let totalHeight = proxy.size.height - let utilityWidth = totalWidth * 0.225 - let standardColumnWidth = (totalWidth - utilityWidth) / 5 - let rowHeight = totalHeight / 4 - - ZStack { - mtMathImage("Numbers Keyboard") - .resizable() - .frame(width: totalWidth, height: totalHeight) - - HStack(spacing: 0) { - VStack(spacing: 0) { - ForEach(featureItems) { item in - KeyButton(item).frame(width: standardColumnWidth, height: rowHeight) - } - } - mainColumnsSection(columnWidth: standardColumnWidth, rowHeight: rowHeight) - VStack(spacing: 0) { - KeyButton(utilityBackspace).frame(width: utilityWidth, height: rowHeight) - KeyButton(utilityEnter).frame(width: utilityWidth, height: rowHeight * 2) - KeyButton(utilityDismiss).frame(width: utilityWidth, height: rowHeight) - } - } - } - .frame(width: totalWidth, height: totalHeight) - } - } - - // 2) Main four-column block (numbers left/middle/right + operators) using Grid - private func mainColumnsSection(columnWidth: CGFloat, rowHeight: CGFloat) -> some View { - let columns = [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] - return Grid(horizontalSpacing: 0, verticalSpacing: 0) { - ForEach(0..<4, id: \.self) { row in - GridRow { - ForEach(0..<4, id: \.self) { column in - KeyButton(columns[column][row]) - .frame(width: columnWidth, height: rowHeight) - } - } - } - } + private var utilityItems: KeyboardUtilityItems { + commonUtilityItems(onAction: onAction) } - private var featureItems: [KeyboardCell] { - [ - .text( - label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("x") }, enabled: keyboardState.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), - .text( - label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, - action: { onInsertText("y") }, enabled: keyboardState.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), - .image( - imageName: "Fraction", - action: { onInsertText(MTSymbolFractionSlash) }, enabled: keyboardState.fractionsAllowed, - accessibilityLabel: "Fraction", - pressedAsset: "Keyboard-marine-pressed"), - .image( - imageName: "Exponent", - action: { onInsertText("^") }, enabled: true, accessibilityLabel: "Exponent", - pressedAsset: "Keyboard-marine-pressed", - overlayAsset: keyboardState.exponentHighlighted ? "blue-button-highlighted" : nil), - ] + var body: some View { + KeyboardBaseLayoutView( + backgroundImageName: "Numbers Keyboard", + featureItems: commonFeatureItems(keyboardState: keyboardState, onAction: onAction), + middleColumns: [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems], + utilityBackspace: utilityItems.backspace, + utilityEnter: utilityItems.enter, + utilityDismiss: utilityItems.dismiss + ) } private var numbersLeftItems: [KeyboardCell] { [ .text( - label: "7", tone: .dark, action: { onInsertText("7") }, + label: "7", tone: .dark, action: { onAction(.insertText("7")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "4", tone: .dark, action: { onInsertText("4") }, + label: "4", tone: .dark, action: { onAction(.insertText("4")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "1", tone: .dark, action: { onInsertText("1") }, + label: "1", tone: .dark, action: { onAction(.insertText("1")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "0", tone: .dark, action: { onInsertText("0") }, + label: "0", tone: .dark, action: { onAction(.insertText("0")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), ] } @@ -98,16 +40,16 @@ struct NumbersKeyboardView: View { private var numbersMiddleItems: [KeyboardCell] { [ .text( - label: "8", tone: .dark, action: { onInsertText("8") }, + label: "8", tone: .dark, action: { onAction(.insertText("8")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "5", tone: .dark, action: { onInsertText("5") }, + label: "5", tone: .dark, action: { onAction(.insertText("5")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "2", tone: .dark, action: { onInsertText("2") }, + label: "2", tone: .dark, action: { onAction(.insertText("2")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: ".", tone: .dark, action: { onInsertText(".") }, + label: ".", tone: .dark, action: { onAction(.insertText(".")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), ] } @@ -115,17 +57,17 @@ struct NumbersKeyboardView: View { private var numbersRightItems: [KeyboardCell] { [ .text( - label: "9", tone: .dark, action: { onInsertText("9") }, + label: "9", tone: .dark, action: { onAction(.insertText("9")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "6", tone: .dark, action: { onInsertText("6") }, + label: "6", tone: .dark, action: { onAction(.insertText("6")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( - label: "3", tone: .dark, action: { onInsertText("3") }, + label: "3", tone: .dark, action: { onAction(.insertText("3")) }, enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), .text( label: "=", tone: keyboardState.equalsAllowed ? .dark : .disabled, - action: { onInsertText("=") }, enabled: keyboardState.equalsAllowed, + action: { onAction(.insertText("=")) }, enabled: keyboardState.equalsAllowed, pressedAsset: "Keyboard-grey-pressed", overlayAsset: keyboardState.equalsAllowed ? nil : "num-button-disabled"), ] @@ -134,37 +76,17 @@ struct NumbersKeyboardView: View { private var operatorItems: [KeyboardCell] { [ .text( - label: "÷", tone: .dark, action: { onInsertText("÷") }, + label: "÷", tone: .dark, action: { onAction(.insertText("÷")) }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), .text( - label: "×", tone: .dark, action: { onInsertText("×") }, + label: "×", tone: .dark, action: { onAction(.insertText("×")) }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), .text( - label: "-", tone: .dark, action: { onInsertText("-") }, + label: "-", tone: .dark, action: { onAction(.insertText("-")) }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), .text( - label: "+", tone: .dark, action: { onInsertText("+") }, + label: "+", tone: .dark, action: { onAction(.insertText("+")) }, enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), ] } - - private var utilityBackspace: KeyboardCell { - .image( - imageName: "Backspace", - action: onBackspace, enabled: true, accessibilityLabel: "Backspace", - pressedAsset: "Keyboard-grey-pressed") - } - - private var utilityEnter: KeyboardCell { - .text( - label: "Enter", tone: .light, - action: { onInsertText("\n") }, enabled: true, pressedAsset: "Keyboard-grey-pressed") - } - - private var utilityDismiss: KeyboardCell { - .image( - imageName: "Keyboard Down", - action: onDismiss, enabled: true, accessibilityLabel: "Dismiss keyboard", - pressedAsset: "Keyboard-grey-pressed") - } } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift new file mode 100644 index 0000000..131dffb --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift @@ -0,0 +1,58 @@ +import Foundation +import SwiftUI + +struct OperationsKeyboardView: View { + let keyboardState: KeyboardState + let onAction: (KeyboardAction) -> Void + + private var utilityItems: KeyboardUtilityItems { + commonUtilityItems(onAction: onAction) + } + + var body: some View { + KeyboardBaseLayoutView( + backgroundImageName: "Operations Keyboard", + featureItems: commonFeatureItems(keyboardState: keyboardState, onAction: onAction), + middleColumns: [groupingLeftItems, groupingRightItems, relationItems, punctuationItems], + utilityBackspace: utilityItems.backspace, + utilityEnter: utilityItems.enter, + utilityDismiss: utilityItems.dismiss + ) + } + + private var groupingLeftItems: [KeyboardCell] { + [ + .text(label: "(", tone: .dark, action: { onAction(.insertText("(")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "[", tone: .dark, action: { onAction(.insertText("[")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "{", tone: .dark, action: { onAction(.insertText("{")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "!", tone: .dark, action: { onAction(.insertText("!")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + ] + } + + private var groupingRightItems: [KeyboardCell] { + [ + .text(label: ")", tone: .dark, action: { onAction(.insertText(")")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "]", tone: .dark, action: { onAction(.insertText("]")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "}", tone: .dark, action: { onAction(.insertText("}")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "∞", tone: .dark, action: { onAction(.insertText("∞")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + ] + } + + private var relationItems: [KeyboardCell] { + [ + .text(label: "<", tone: .dark, action: { onAction(.insertText("<")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "≤", tone: .dark, action: { onAction(.insertText("≤")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "|□|", tone: .dark, action: { onAction(.insertText("||")) }, enabled: true, accessibilityLabel: "Absolute value", pressedAsset: "Keyboard-orange-pressed"), + .text(label: ":", tone: .dark, action: { onAction(.insertText(":")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + ] + } + + private var punctuationItems: [KeyboardCell] { + [ + .text(label: ">", tone: .dark, action: { onAction(.insertText(">")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "≥", tone: .dark, action: { onAction(.insertText("≥")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: "%", tone: .dark, action: { onAction(.insertText("%")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + .text(label: ",", tone: .dark, action: { onAction(.insertText(",")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), + ] + } +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/SwiftUIKeyboardHostView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/SwiftUIKeyboardHostView.swift new file mode 100644 index 0000000..b5a876e --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/SwiftUIKeyboardHostView.swift @@ -0,0 +1,88 @@ +#if os(iOS) + + import SwiftUI + import UIKit + + final class SwiftUIKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { + typealias RootViewBuilder = (KeyboardState, @escaping (KeyboardAction) -> Void) -> AnyView + + private var keyboardState = KeyboardState() + private weak var editingTarget: (any UIView & UIKeyInput)? + private let rootViewBuilder: RootViewBuilder + private lazy var hostingController = UIHostingController(rootView: AnyView(EmptyView())) + + init(rootViewBuilder: @escaping RootViewBuilder) { + self.rootViewBuilder = rootViewBuilder + super.init(frame: .zero) + commonInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + editingTarget = textView + } + + func applyKeyboardState(_ state: KeyboardState) { + updateState { $0 = state } + } + + var enableInputClicksWhenVisible: Bool { true } + + private func commonInit() { + backgroundColor = .white + + let hostedView = hostingController.view! + if #available(iOS 16.4, *) { + hostingController.safeAreaRegions = [] + } + hostedView.backgroundColor = .clear + hostedView.translatesAutoresizingMaskIntoConstraints = false + addSubview(hostedView) + + NSLayoutConstraint.activate([ + hostedView.topAnchor.constraint(equalTo: topAnchor), + hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), + hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), + hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + + hostingController.rootView = makeRootView() + } + + private func makeRootView() -> AnyView { + rootViewBuilder(keyboardState, { [weak self] action in self?.handle(action) }) + } + + private func handle(_ action: KeyboardAction) { + playClickForCustomKeyTap() + + switch action { + case .insertText(let text): + editingTarget?.insertText(text) + case .backspace: + editingTarget?.deleteBackward() + case .dismiss: + editingTarget?.resignFirstResponder() + } + } + + private func playClickForCustomKeyTap() { + UIDevice.current.playInputClick() + } + + private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + let previousState = self.keyboardState + update(&self.keyboardState) + guard self.keyboardState != previousState else { return } + self.hostingController.rootView = self.makeRootView() + } + } + } + +#endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index e289bf0..8fffdfc 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -38,7 +38,7 @@ private lazy var keyboards: [KeyboardTab: (UIView & KeyboardConfigurable)] = [ .numbers: makeNumbersKeyboard(), .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), - .operations: makeKeyboard(named: KeyboardTab.operations.nibName), + .operations: makeOperationsKeyboard(), .functions: makeKeyboard(named: KeyboardTab.functions.nibName), .letters: makeKeyboard(named: KeyboardTab.letters.nibName), ] @@ -67,12 +67,28 @@ ]) } - private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { - let keyboard = NumbersKeyboardHostView() + private func makeSwiftUIKeyboard( + @ViewBuilder content: @escaping (KeyboardState, @escaping (KeyboardAction) -> Void) -> some View + ) -> UIView & KeyboardConfigurable { + let keyboard = SwiftUIKeyboardHostView { state, onAction in + AnyView(content(state, onAction)) + } keyboard.translatesAutoresizingMaskIntoConstraints = false return keyboard } + private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { + makeSwiftUIKeyboard { state, onAction in + NumbersKeyboardView(keyboardState: state, onAction: onAction) + } + } + + private func makeOperationsKeyboard() -> UIView & KeyboardConfigurable { + makeSwiftUIKeyboard { state, onAction in + OperationsKeyboardView(keyboardState: state, onAction: onAction) + } + } + private func makeKeyboard(named nibName: String) -> UIView & KeyboardConfigurable { let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() let keyboard = UINib(nibName: nibName, bundle: bundle) From 0c87fa5b5bbae5392d97ed08f3406dcbf5303e68 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 00:43:03 +0000 Subject: [PATCH 074/133] Refactor --- ...IView.swift => KeyboardConfigurable.swift} | 0 .../Keyboard/KeyboardViewComponents.swift | 104 ----------- ...ostView.swift => MainKeyboardUIView.swift} | 28 ++- .../Keyboard/MainKeyboardView.swift | 97 +++++++++++ .../Keyboard/NumbersKeyboardView.swift | 161 ++++++++---------- .../Keyboard/OperationsKeyboardView.swift | 125 ++++++++------ .../KeyboardContainerView.swift | 18 +- .../MTMathKeyboardSwiftUIRootView.swift | 7 +- 8 files changed, 266 insertions(+), 274 deletions(-) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/{NumbersKeyboardUIView.swift => KeyboardConfigurable.swift} (100%) delete mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/{SwiftUIKeyboardHostView.swift => MainKeyboardUIView.swift} (76%) create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardConfigurable.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardUIView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardConfigurable.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift deleted file mode 100644 index 5329574..0000000 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardViewComponents.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Foundation -import MathEditor -import SwiftUI - -struct KeyboardBaseLayoutView: View { - let backgroundImageName: String - let featureItems: [KeyboardCell] - let middleColumns: [[KeyboardCell]] - let utilityBackspace: KeyboardCell - let utilityEnter: KeyboardCell - let utilityDismiss: KeyboardCell - - var body: some View { - GeometryReader { proxy in - let totalWidth = proxy.size.width - let totalHeight = proxy.size.height - let utilityWidth = totalWidth * 0.225 - let standardColumnWidth = (totalWidth - utilityWidth) / 5 - let rowHeight = totalHeight / 4 - - ZStack { - mtMathImage(backgroundImageName) - .resizable() - .frame(width: totalWidth, height: totalHeight) - - HStack(spacing: 0) { - VStack(spacing: 0) { - ForEach(featureItems) { item in - KeyButton(item).frame(width: standardColumnWidth, height: rowHeight) - } - } - - Grid(horizontalSpacing: 0, verticalSpacing: 0) { - ForEach(0..<4, id: \.self) { row in - GridRow { - ForEach(0..<4, id: \.self) { column in - KeyButton(middleColumns[column][row]) - .frame(width: standardColumnWidth, height: rowHeight) - } - } - } - } - - VStack(spacing: 0) { - KeyButton(utilityBackspace).frame(width: utilityWidth, height: rowHeight) - KeyButton(utilityEnter).frame(width: utilityWidth, height: rowHeight * 2) - KeyButton(utilityDismiss).frame(width: utilityWidth, height: rowHeight) - } - } - } - .frame(width: totalWidth, height: totalHeight) - } - } -} - -func commonFeatureItems( - keyboardState: KeyboardState, - onAction: @escaping (KeyboardAction) -> Void -) -> [KeyboardCell] { - [ - .text( - label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, - action: { onAction(.insertText("x")) }, enabled: keyboardState.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), - .text( - label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, - action: { onAction(.insertText("y")) }, enabled: keyboardState.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), - .image( - imageName: "Fraction", - action: { onAction(.insertText(MTSymbolFractionSlash)) }, - enabled: keyboardState.fractionsAllowed, - accessibilityLabel: "Fraction", - pressedAsset: "Keyboard-marine-pressed"), - .image( - imageName: "Exponent", - action: { onAction(.insertText("^")) }, enabled: true, accessibilityLabel: "Exponent", - pressedAsset: "Keyboard-marine-pressed", - overlayAsset: keyboardState.exponentHighlighted ? "blue-button-highlighted" : nil), - ] -} - -struct KeyboardUtilityItems { - let backspace: KeyboardCell - let enter: KeyboardCell - let dismiss: KeyboardCell -} - -func commonUtilityItems(onAction: @escaping (KeyboardAction) -> Void) -> KeyboardUtilityItems { - KeyboardUtilityItems( - backspace: .image( - imageName: "Backspace", - action: { onAction(.backspace) }, enabled: true, accessibilityLabel: "Backspace", - pressedAsset: "Keyboard-grey-pressed"), - enter: .text( - label: "Enter", tone: .light, - action: { onAction(.insertText("\n")) }, enabled: true, - pressedAsset: "Keyboard-grey-pressed"), - dismiss: .image( - imageName: "Keyboard Down", - action: { onAction(.dismiss) }, enabled: true, accessibilityLabel: "Dismiss keyboard", - pressedAsset: "Keyboard-grey-pressed") - ) -} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/SwiftUIKeyboardHostView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardUIView.swift similarity index 76% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/SwiftUIKeyboardHostView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardUIView.swift index b5a876e..1af0423 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/SwiftUIKeyboardHostView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardUIView.swift @@ -3,13 +3,14 @@ import SwiftUI import UIKit - final class SwiftUIKeyboardHostView: UIView, KeyboardConfigurable, UIInputViewAudioFeedback { - typealias RootViewBuilder = (KeyboardState, @escaping (KeyboardAction) -> Void) -> AnyView + final class MainKeyboardUIView: UIView, KeyboardConfigurable { + typealias RootViewBuilder = (KeyboardState, @escaping (KeyboardAction) -> Void) -> + MainKeyboardView private var keyboardState = KeyboardState() - private weak var editingTarget: (any UIView & UIKeyInput)? + private weak var textInput: (any UIView & UIKeyInput)? private let rootViewBuilder: RootViewBuilder - private lazy var hostingController = UIHostingController(rootView: AnyView(EmptyView())) + private lazy var hostingController = UIHostingController(rootView: makeRootView()) init(rootViewBuilder: @escaping RootViewBuilder) { self.rootViewBuilder = rootViewBuilder @@ -23,22 +24,19 @@ } func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { - editingTarget = textView + textInput = textView } func applyKeyboardState(_ state: KeyboardState) { updateState { $0 = state } } - var enableInputClicksWhenVisible: Bool { true } - private func commonInit() { + translatesAutoresizingMaskIntoConstraints = false backgroundColor = .white let hostedView = hostingController.view! - if #available(iOS 16.4, *) { - hostingController.safeAreaRegions = [] - } + hostingController.safeAreaRegions = [] hostedView.backgroundColor = .clear hostedView.translatesAutoresizingMaskIntoConstraints = false addSubview(hostedView) @@ -53,8 +51,8 @@ hostingController.rootView = makeRootView() } - private func makeRootView() -> AnyView { - rootViewBuilder(keyboardState, { [weak self] action in self?.handle(action) }) + private func makeRootView() -> MainKeyboardView { + rootViewBuilder(keyboardState) { [weak self] action in self?.handle(action) } } private func handle(_ action: KeyboardAction) { @@ -62,11 +60,11 @@ switch action { case .insertText(let text): - editingTarget?.insertText(text) + textInput?.insertText(text) case .backspace: - editingTarget?.deleteBackward() + textInput?.deleteBackward() case .dismiss: - editingTarget?.resignFirstResponder() + textInput?.resignFirstResponder() } } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardView.swift new file mode 100644 index 0000000..0e27a8d --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardView.swift @@ -0,0 +1,97 @@ +import Foundation +import MathEditor +import SwiftUI + +struct MainKeyboardView: View { + let backgroundImageName: String + let middleColumns: [[KeyboardCell]] + let state: KeyboardState + let onAction: (KeyboardAction) -> Void + + var body: some View { + GeometryReader { proxy in + let totalWidth = proxy.size.width + let totalHeight = proxy.size.height + let utilityWidth = totalWidth * 0.225 + let standardColumnWidth = (totalWidth - utilityWidth) / 5 + let rowHeight = totalHeight / 4 + + ZStack { + mtMathImage(backgroundImageName) + .resizable() + .frame(width: totalWidth, height: totalHeight) + + HStack(spacing: 0) { + VStack(spacing: 0) { + ForEach(featuresColumn) { item in + KeyButton(item).frame(width: standardColumnWidth, height: rowHeight) + } + } + + Grid(horizontalSpacing: 0, verticalSpacing: 0) { + ForEach(0..<4, id: \.self) { row in + GridRow { + ForEach(0..<4, id: \.self) { column in + KeyButton(middleColumns[column][row]) + .frame(width: standardColumnWidth, height: rowHeight) + } + } + } + } + + VStack(spacing: 0) { + KeyButton(backspaceCell).frame(width: utilityWidth, height: rowHeight) + KeyButton(enterCell).frame(width: utilityWidth, height: rowHeight * 2) + KeyButton(dismissCell).frame(width: utilityWidth, height: rowHeight) + } + } + } + .frame(width: totalWidth, height: totalHeight) + } + } +} + +extension MainKeyboardView { + private var featuresColumn: [KeyboardCell] { + [ + .text( + label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, + action: { onAction(.insertText("x")) }, enabled: state.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), + .text( + label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, + action: { onAction(.insertText("y")) }, enabled: state.variablesAllowed, + pressedAsset: "Keyboard-marine-pressed"), + .image( + imageName: "Fraction", + action: { onAction(.insertText(MTSymbolFractionSlash)) }, + enabled: state.fractionsAllowed, + accessibilityLabel: "Fraction", + pressedAsset: "Keyboard-marine-pressed"), + .image( + imageName: "Exponent", + action: { onAction(.insertText("^")) }, enabled: true, accessibilityLabel: "Exponent", + pressedAsset: "Keyboard-marine-pressed", + overlayAsset: state.exponentHighlighted ? "blue-button-highlighted" : nil), + ] + } + private var backspaceCell: KeyboardCell { + KeyboardCell.image( + imageName: "Backspace", + action: { onAction(.backspace) }, enabled: true, accessibilityLabel: "Backspace", + pressedAsset: "Keyboard-grey-pressed") + } + private var enterCell: KeyboardCell { + KeyboardCell.text( + label: "Enter", tone: .light, + action: { onAction(.insertText("\n")) }, enabled: true, + pressedAsset: "Keyboard-grey-pressed") + } + private var dismissCell: KeyboardCell { + KeyboardCell.image( + imageName: "Keyboard Down", + action: { onAction(.dismiss) }, enabled: true, accessibilityLabel: "Dismiss keyboard", + pressedAsset: "Keyboard-grey-pressed") + } + +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift index ea11051..0d36141 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift @@ -1,92 +1,79 @@ import Foundation import SwiftUI -struct NumbersKeyboardView: View { - let keyboardState: KeyboardState - let onAction: (KeyboardAction) -> Void - - private var utilityItems: KeyboardUtilityItems { - commonUtilityItems(onAction: onAction) - } - - var body: some View { - KeyboardBaseLayoutView( - backgroundImageName: "Numbers Keyboard", - featureItems: commonFeatureItems(keyboardState: keyboardState, onAction: onAction), - middleColumns: [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems], - utilityBackspace: utilityItems.backspace, - utilityEnter: utilityItems.enter, - utilityDismiss: utilityItems.dismiss - ) - } - - private var numbersLeftItems: [KeyboardCell] { - [ - .text( - label: "7", tone: .dark, action: { onAction(.insertText("7")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "4", tone: .dark, action: { onAction(.insertText("4")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "1", tone: .dark, action: { onAction(.insertText("1")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "0", tone: .dark, action: { onAction(.insertText("0")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - ] - } - - private var numbersMiddleItems: [KeyboardCell] { - [ - .text( - label: "8", tone: .dark, action: { onAction(.insertText("8")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "5", tone: .dark, action: { onAction(.insertText("5")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "2", tone: .dark, action: { onAction(.insertText("2")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: ".", tone: .dark, action: { onAction(.insertText(".")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - ] - } - - private var numbersRightItems: [KeyboardCell] { - [ - .text( - label: "9", tone: .dark, action: { onAction(.insertText("9")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "6", tone: .dark, action: { onAction(.insertText("6")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "3", tone: .dark, action: { onAction(.insertText("3")) }, - enabled: keyboardState.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), - .text( - label: "=", tone: keyboardState.equalsAllowed ? .dark : .disabled, - action: { onAction(.insertText("=")) }, enabled: keyboardState.equalsAllowed, - pressedAsset: "Keyboard-grey-pressed", - overlayAsset: keyboardState.equalsAllowed ? nil : "num-button-disabled"), - ] - } +func numbersKeyboardView( + state: KeyboardState, + onAction: @escaping (KeyboardAction) -> Void +) -> MainKeyboardView { + MainKeyboardView( + backgroundImageName: "Numbers Keyboard", + middleColumns: makeNumbersGrid(state: state, onAction: onAction), + state: state, + onAction: onAction + ) +} - private var operatorItems: [KeyboardCell] { - [ - .text( - label: "÷", tone: .dark, action: { onAction(.insertText("÷")) }, - enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text( - label: "×", tone: .dark, action: { onAction(.insertText("×")) }, - enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text( - label: "-", tone: .dark, action: { onAction(.insertText("-")) }, - enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - .text( - label: "+", tone: .dark, action: { onAction(.insertText("+")) }, - enabled: keyboardState.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), - ] - } +private func makeNumbersGrid( + state: KeyboardState, + onAction: @escaping (KeyboardAction) -> Void +) -> [[KeyboardCell]] { + let numbersLeftItems: [KeyboardCell] = [ + .text( + label: "7", tone: .dark, action: { onAction(.insertText("7")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "4", tone: .dark, action: { onAction(.insertText("4")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "1", tone: .dark, action: { onAction(.insertText("1")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "0", tone: .dark, action: { onAction(.insertText("0")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + ] + let numbersMiddleItems: [KeyboardCell] = [ + .text( + label: "8", tone: .dark, action: { onAction(.insertText("8")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "5", tone: .dark, action: { onAction(.insertText("5")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "2", tone: .dark, action: { onAction(.insertText("2")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: ".", tone: .dark, action: { onAction(.insertText(".")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + ] + let numbersRightItems: [KeyboardCell] = [ + .text( + label: "9", tone: .dark, action: { onAction(.insertText("9")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "6", tone: .dark, action: { onAction(.insertText("6")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "3", tone: .dark, action: { onAction(.insertText("3")) }, + enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"), + .text( + label: "=", tone: state.equalsAllowed ? .dark : .disabled, + action: { onAction(.insertText("=")) }, enabled: state.equalsAllowed, + pressedAsset: "Keyboard-grey-pressed", + overlayAsset: state.equalsAllowed ? nil : "num-button-disabled"), + ] + let operatorItems: [KeyboardCell] = [ + .text( + label: "÷", tone: .dark, action: { onAction(.insertText("÷")) }, + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "×", tone: .dark, action: { onAction(.insertText("×")) }, + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "-", tone: .dark, action: { onAction(.insertText("-")) }, + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "+", tone: .dark, action: { onAction(.insertText("+")) }, + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + ] + return [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift index 131dffb..3bd03de 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift @@ -1,58 +1,77 @@ import Foundation import SwiftUI -struct OperationsKeyboardView: View { - let keyboardState: KeyboardState - let onAction: (KeyboardAction) -> Void - - private var utilityItems: KeyboardUtilityItems { - commonUtilityItems(onAction: onAction) - } - - var body: some View { - KeyboardBaseLayoutView( - backgroundImageName: "Operations Keyboard", - featureItems: commonFeatureItems(keyboardState: keyboardState, onAction: onAction), - middleColumns: [groupingLeftItems, groupingRightItems, relationItems, punctuationItems], - utilityBackspace: utilityItems.backspace, - utilityEnter: utilityItems.enter, - utilityDismiss: utilityItems.dismiss - ) - } - - private var groupingLeftItems: [KeyboardCell] { - [ - .text(label: "(", tone: .dark, action: { onAction(.insertText("(")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "[", tone: .dark, action: { onAction(.insertText("[")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "{", tone: .dark, action: { onAction(.insertText("{")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "!", tone: .dark, action: { onAction(.insertText("!")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - ] - } - - private var groupingRightItems: [KeyboardCell] { - [ - .text(label: ")", tone: .dark, action: { onAction(.insertText(")")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "]", tone: .dark, action: { onAction(.insertText("]")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "}", tone: .dark, action: { onAction(.insertText("}")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "∞", tone: .dark, action: { onAction(.insertText("∞")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - ] - } - - private var relationItems: [KeyboardCell] { - [ - .text(label: "<", tone: .dark, action: { onAction(.insertText("<")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "≤", tone: .dark, action: { onAction(.insertText("≤")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "|□|", tone: .dark, action: { onAction(.insertText("||")) }, enabled: true, accessibilityLabel: "Absolute value", pressedAsset: "Keyboard-orange-pressed"), - .text(label: ":", tone: .dark, action: { onAction(.insertText(":")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - ] - } +func operationsKeyboardView( + state: KeyboardState, + onAction: @escaping (KeyboardAction) -> Void +) -> MainKeyboardView { + MainKeyboardView( + backgroundImageName: "Operations Keyboard", + middleColumns: makeOperationsGrid(state: state, onAction: onAction), + state: state, + onAction: onAction + ) +} - private var punctuationItems: [KeyboardCell] { - [ - .text(label: ">", tone: .dark, action: { onAction(.insertText(">")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "≥", tone: .dark, action: { onAction(.insertText("≥")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: "%", tone: .dark, action: { onAction(.insertText("%")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - .text(label: ",", tone: .dark, action: { onAction(.insertText(",")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), - ] - } +private func makeOperationsGrid( + state: KeyboardState, + onAction: @escaping (KeyboardAction) -> Void +) -> [[KeyboardCell]] { + let groupingLeftItems: [KeyboardCell] = [ + .text( + label: "(", tone: .dark, action: { onAction(.insertText("(")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "[", tone: .dark, action: { onAction(.insertText("[")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "{", tone: .dark, action: { onAction(.insertText("{")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "!", tone: .dark, action: { onAction(.insertText("!")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + ] + let groupingRightItems: [KeyboardCell] = [ + .text( + label: ")", tone: .dark, action: { onAction(.insertText(")")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "]", tone: .dark, action: { onAction(.insertText("]")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "}", tone: .dark, action: { onAction(.insertText("}")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "∞", tone: .dark, action: { onAction(.insertText("∞")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + ] + let relationItems: [KeyboardCell] = [ + .text( + label: "<", tone: .dark, action: { onAction(.insertText("<")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "≤", tone: .dark, action: { onAction(.insertText("≤")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "|□|", tone: .dark, action: { onAction(.insertText("||")) }, enabled: true, + accessibilityLabel: "Absolute value", pressedAsset: "Keyboard-orange-pressed"), + .text( + label: ":", tone: .dark, action: { onAction(.insertText(":")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + ] + let punctuationItems: [KeyboardCell] = [ + .text( + label: ">", tone: .dark, action: { onAction(.insertText(">")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "≥", tone: .dark, action: { onAction(.insertText("≥")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: "%", tone: .dark, action: { onAction(.insertText("%")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + .text( + label: ",", tone: .dark, action: { onAction(.insertText(",")) }, enabled: true, + pressedAsset: "Keyboard-orange-pressed"), + ] + return [groupingLeftItems, groupingRightItems, relationItems, punctuationItems] } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index 8fffdfc..bf24b09 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -67,25 +67,15 @@ ]) } - private func makeSwiftUIKeyboard( - @ViewBuilder content: @escaping (KeyboardState, @escaping (KeyboardAction) -> Void) -> some View - ) -> UIView & KeyboardConfigurable { - let keyboard = SwiftUIKeyboardHostView { state, onAction in - AnyView(content(state, onAction)) - } - keyboard.translatesAutoresizingMaskIntoConstraints = false - return keyboard - } - private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { - makeSwiftUIKeyboard { state, onAction in - NumbersKeyboardView(keyboardState: state, onAction: onAction) + MainKeyboardUIView { state, onAction in + numbersKeyboardView(state: state, onAction: onAction) } } private func makeOperationsKeyboard() -> UIView & KeyboardConfigurable { - makeSwiftUIKeyboard { state, onAction in - OperationsKeyboardView(keyboardState: state, onAction: onAction) + MainKeyboardUIView { state, onAction in + operationsKeyboardView(state: state, onAction: onAction) } } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index 469e0df..049b967 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -5,7 +5,8 @@ import SwiftUI import UIKit - public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboard { + public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboard, UIInputViewAudioFeedback + { private static let defaultTab: KeyboardTab = .numbers private static let shared = MTMathKeyboardSwiftUIRootView() @@ -15,6 +16,10 @@ rootView: makeRootView() ) + public var enableInputClicksWhenVisible: Bool { + true + } + public override init(frame: CGRect) { super.init(frame: frame) commonInit() From f0aed383018df7f0d8a05782e8477b466be97e12 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 00:49:23 +0000 Subject: [PATCH 075/133] Implement SwiftUI functions keyboard tab --- .../Keyboard/FunctionsKeyboardView.swift | 98 +++++++++++++++++++ .../KeyboardContainerView.swift | 8 +- 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/FunctionsKeyboardView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/FunctionsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/FunctionsKeyboardView.swift new file mode 100644 index 0000000..bb6df9d --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/FunctionsKeyboardView.swift @@ -0,0 +1,98 @@ +import Foundation +import MathEditor +import SwiftUI + +func functionsKeyboardView( + state: KeyboardState, + onAction: @escaping (KeyboardAction) -> Void +) -> MainKeyboardView { + MainKeyboardView( + backgroundImageName: "Functions Keyboard", + middleColumns: makeFunctionsGrid(state: state, onAction: onAction), + state: state, + onAction: onAction + ) +} + +private func makeFunctionsGrid( + state: KeyboardState, + onAction: @escaping (KeyboardAction) -> Void +) -> [[KeyboardCell]] { + let trigLeftItems: [KeyboardCell] = [ + .text( + label: "sin", tone: .dark, action: { onAction(.insertText("sin")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "sec", tone: .dark, action: { onAction(.insertText("sec")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "log", tone: .dark, action: { onAction(.insertText("log")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .image( + imageName: "Subscript", + action: { onAction(.insertText("_")) }, + enabled: true, + accessibilityLabel: "Subscript", + pressedAsset: "Keyboard-green-pressed"), + ] + + let trigMiddleItems: [KeyboardCell] = [ + .text( + label: "cos", tone: .dark, action: { onAction(.insertText("cos")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "csc", tone: .dark, action: { onAction(.insertText("csc")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "ln", tone: .dark, action: { onAction(.insertText("ln")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .image( + imageName: "Sqrt", + action: { onAction(.insertText(MTSymbolSquareRoot)) }, + enabled: true, + accessibilityLabel: "Square root", + pressedAsset: "Keyboard-green-pressed", + overlayAsset: state.squareRootHighlighted ? "Keyboard-green-pressed" : nil), + ] + + let trigRightItems: [KeyboardCell] = [ + .text( + label: "tan", tone: .dark, action: { onAction(.insertText("tan")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "cot", tone: .dark, action: { onAction(.insertText("cot")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "π", tone: .dark, fontName: "TimesNewRomanPSMT", + action: { onAction(.insertText("π")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .image( + imageName: "Sqrt with Power", + action: { onAction(.insertText(MTSymbolCubeRoot)) }, + enabled: true, + accessibilityLabel: "Root with power", + pressedAsset: "Keyboard-green-pressed", + overlayAsset: state.radicalHighlighted ? "Keyboard-green-pressed" : nil), + ] + + let constantsItems: [KeyboardCell] = [ + .text( + label: "θ", tone: .dark, fontName: "TimesNewRomanPSMT", + action: { onAction(.insertText("θ")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "π", tone: .dark, fontName: "TimesNewRomanPSMT", + action: { onAction(.insertText("π")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), + .text( + label: "", tone: .dark, + action: {}, enabled: false, + accessibilityLabel: "Unused"), + .text( + label: "", tone: .dark, + action: {}, enabled: false, + accessibilityLabel: "Unused"), + ] + + return [trigLeftItems, trigMiddleItems, trigRightItems, constantsItems] +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index bf24b09..a0550d2 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -39,7 +39,7 @@ .numbers: makeNumbersKeyboard(), .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), .operations: makeOperationsKeyboard(), - .functions: makeKeyboard(named: KeyboardTab.functions.nibName), + .functions: makeFunctionsKeyboard(), .letters: makeKeyboard(named: KeyboardTab.letters.nibName), ] @@ -79,6 +79,12 @@ } } + private func makeFunctionsKeyboard() -> UIView & KeyboardConfigurable { + MainKeyboardUIView { state, onAction in + functionsKeyboardView(state: state, onAction: onAction) + } + } + private func makeKeyboard(named nibName: String) -> UIView & KeyboardConfigurable { let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() let keyboard = UINib(nibName: nibName, bundle: bundle) From efcdb496400b18fc6c68ff4a015b48d95332ee9a Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 01:37:34 +0000 Subject: [PATCH 076/133] letters keyboard --- .../KeyboardContainerView.swift | 14 +- .../FunctionsKeyboardView.swift | 0 .../{Keyboard => Keyboards}/KeyButton.swift | 0 .../KeyboardAction.swift | 0 .../KeyboardCell.swift | 0 .../KeyboardConfigurable.swift | 0 .../Keyboards/LettersKeyboardUIView.swift | 79 ++++++++ .../Keyboards/LettersKeyboardView.swift | 187 ++++++++++++++++++ .../MTMathKeyboardCompat.swift | 0 .../MainKeyboardUIView.swift | 9 +- .../MainKeyboardView.swift | 0 .../NumbersKeyboardView.swift | 0 .../OperationsKeyboardView.swift | 0 .../MTMathKeyboardSwiftUIRootView.swift | 9 +- .../MTView+AutoLayout.swift | 27 +++ 15 files changed, 301 insertions(+), 24 deletions(-) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/FunctionsKeyboardView.swift (100%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/KeyButton.swift (100%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/KeyboardAction.swift (100%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/KeyboardCell.swift (100%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/KeyboardConfigurable.swift (100%) create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/MTMathKeyboardCompat.swift (100%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/MainKeyboardUIView.swift (85%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/MainKeyboardView.swift (100%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/NumbersKeyboardView.swift (100%) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/{Keyboard => Keyboards}/OperationsKeyboardView.swift (100%) create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index a0550d2..74f8437 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -40,7 +40,7 @@ .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), .operations: makeOperationsKeyboard(), .functions: makeFunctionsKeyboard(), - .letters: makeKeyboard(named: KeyboardTab.letters.nibName), + .letters: makeLettersKeyboard(), ] fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { @@ -58,13 +58,7 @@ currentKeyboard?.removeFromSuperview() currentKeyboard = keyboard addSubview(keyboard) - - NSLayoutConstraint.activate([ - keyboard.topAnchor.constraint(equalTo: topAnchor), - keyboard.leadingAnchor.constraint(equalTo: leadingAnchor), - keyboard.trailingAnchor.constraint(equalTo: trailingAnchor), - keyboard.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + keyboard.pinToSuperview() } private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { @@ -85,6 +79,10 @@ } } + private func makeLettersKeyboard() -> UIView & KeyboardConfigurable { + LettersKeyboardUIView() + } + private func makeKeyboard(named nibName: String) -> UIView & KeyboardConfigurable { let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() let keyboard = UINib(nibName: nibName, bundle: bundle) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/FunctionsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/FunctionsKeyboardView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyButton.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyButton.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardAction.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardAction.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardAction.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardAction.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardCell.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardCell.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardCell.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardCell.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardConfigurable.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/KeyboardConfigurable.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift new file mode 100644 index 0000000..7c6b0a4 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift @@ -0,0 +1,79 @@ +#if os(iOS) + + import SwiftUI + import UIKit + + final class LettersKeyboardUIView: UIView, KeyboardConfigurable { + private var keyboardState = KeyboardState() + private weak var textInput: (any UIView & UIKeyInput)? + private lazy var hostingController = UIHostingController(rootView: makeRootView()) + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + textInput = textView + } + + func applyKeyboardState(_ state: KeyboardState) { + updateState { $0 = state } + } + + private func commonInit() { + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .white + + let hostedView = hostingController.view! + if #available(iOS 16.4, *) { + hostingController.safeAreaRegions = [] + } + hostedView.backgroundColor = .clear + addSubview(hostedView) + hostedView.pinToSuperview() + + hostingController.rootView = makeRootView() + } + + private func makeRootView() -> LettersKeyboardView { + LettersKeyboardView( + state: keyboardState, + onAction: { [weak self] action in self?.handle(action) } + ) + } + + private func handle(_ action: KeyboardAction) { + playClickForCustomKeyTap() + + switch action { + case .insertText(let text): + textInput?.insertText(text) + case .backspace: + textInput?.deleteBackward() + case .dismiss: + textInput?.resignFirstResponder() + } + } + + private func playClickForCustomKeyTap() { + UIDevice.current.playInputClick() + } + + private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + let previousState = self.keyboardState + update(&self.keyboardState) + guard self.keyboardState != previousState else { return } + self.hostingController.rootView = self.makeRootView() + } + } + } + +#endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift new file mode 100644 index 0000000..68508f9 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift @@ -0,0 +1,187 @@ +import Foundation +import SwiftUI + +struct LettersKeyboardView: View { + let state: KeyboardState + let onAction: (KeyboardAction) -> Void + @State private var isLowercase = true + + private var topRow: [String] { + makeLetterRow(["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]) + } + + private var middleRow: [String] { + makeLetterRow(["a", "s", "d", "f", "g", "h", "j", "k", "l"]) + } + + private var bottomRow: [String] { + makeLetterRow(["z", "x", "c", "v", "b", "n", "m"]) + } + + private var greekRow: [GreekKey] { + if isLowercase { + return [ + GreekKey(label: "α", accessibilityLabel: "alpha"), + GreekKey(label: "Δ", accessibilityLabel: "capital delta"), + GreekKey(label: "σ", accessibilityLabel: "sigma"), + GreekKey(label: "μ", accessibilityLabel: "mu"), + GreekKey(label: "λ", accessibilityLabel: "lambda"), + ] + } + + return [ + GreekKey(label: "ρ", accessibilityLabel: "rho"), + GreekKey(label: "ω", accessibilityLabel: "omega"), + GreekKey(label: "Φ", accessibilityLabel: "capital phi"), + GreekKey(label: "ν", accessibilityLabel: "nu"), + GreekKey(label: "β", accessibilityLabel: "beta"), + ] + } + + var body: some View { + GeometryReader { proxy in + let unitWidth = proxy.size.width / 10 + let rowHeight = proxy.size.height / 4 + + ZStack { + mtMathImage("Letters Keyboard") + .resizable() + .frame(width: proxy.size.width, height: proxy.size.height) + + VStack(spacing: 0) { + letterRow(topRow, horizontalInset: 0, unitWidth: unitWidth, rowHeight: rowHeight) + letterRow(middleRow, horizontalInset: unitWidth / 2, unitWidth: unitWidth, rowHeight: rowHeight) + bottomLetterRow(unitWidth: unitWidth, rowHeight: rowHeight) + greekRowView(unitWidth: unitWidth, rowHeight: rowHeight) + } + } + } + } + + @ViewBuilder + private func letterRow( + _ letters: [String], + horizontalInset: CGFloat, + unitWidth: CGFloat, + rowHeight: CGFloat + ) -> some View { + HStack(spacing: 0) { + Color.clear.frame(width: horizontalInset) + ForEach(letters, id: \.self) { letter in + letterCell(letter) + .frame(width: unitWidth, height: rowHeight) + } + Color.clear.frame(width: horizontalInset) + } + } + + @ViewBuilder + private func bottomLetterRow(unitWidth: CGFloat, rowHeight: CGFloat) -> some View { + HStack(spacing: 0) { + KeyButton(shiftCell) + .frame(width: unitWidth * 1.5, height: rowHeight) + + ForEach(bottomRow, id: \.self) { letter in + letterCell(letter) + .frame(width: unitWidth, height: rowHeight) + } + + KeyButton(backspaceCell) + .frame(width: unitWidth * 1.5, height: rowHeight) + } + } + + @ViewBuilder + private func greekRowView(unitWidth: CGFloat, rowHeight: CGFloat) -> some View { + HStack(spacing: 0) { + KeyButton(dismissCell) + .frame(width: unitWidth * 2.5, height: rowHeight) + + ForEach(greekRow) { key in + KeyButton(greekCell(key)) + .frame(width: unitWidth, height: rowHeight) + } + + KeyButton(enterCell) + .frame(width: unitWidth * 2.5, height: rowHeight) + } + } + + private func makeLetterRow(_ letters: [String]) -> [String] { + if isLowercase { + return letters + } + return letters.map { $0.uppercased() } + } + + private func letterCell(_ label: String) -> KeyButton { + KeyButton( + .text( + label: label, + tone: .dark, + action: { onAction(.insertText(label)) }, + enabled: state.variablesAllowed, + pressedAsset: "Keyboard-azure-pressed" + ) + ) + } + + private func greekCell(_ key: GreekKey) -> KeyboardCell { + .text( + label: key.label, + tone: .dark, + fontName: "CourierNewPS-ItalicMT", + action: { onAction(.insertText(key.label)) }, + enabled: state.variablesAllowed, + accessibilityLabel: key.accessibilityLabel, + pressedAsset: "Keyboard-azure-pressed" + ) + } + + private var shiftCell: KeyboardCell { + .image( + imageName: "Shift", + action: { isLowercase.toggle() }, + enabled: true, + accessibilityLabel: "Shift", + pressedAsset: "Keyboard-grey-pressed" + ) + } + + private var backspaceCell: KeyboardCell { + .image( + imageName: "Backspace Small", + action: { onAction(.backspace) }, + enabled: true, + accessibilityLabel: "Backspace", + pressedAsset: "Keyboard-grey-pressed" + ) + } + + private var dismissCell: KeyboardCell { + .image( + imageName: "Keyboard Down", + action: { onAction(.dismiss) }, + enabled: true, + accessibilityLabel: "Dismiss keyboard", + pressedAsset: "Keyboard-grey-pressed" + ) + } + + private var enterCell: KeyboardCell { + .text( + label: "Enter", + tone: .light, + fontName: "HelveticaNeue-Light", + action: { onAction(.insertText("\n")) }, + enabled: true, + pressedAsset: "Keyboard-grey-pressed" + ) + } +} + +private struct GreekKey: Identifiable { + let id = UUID() + let label: String + let accessibilityLabel: String +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MTMathKeyboardCompat.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MTMathKeyboardCompat.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift similarity index 85% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardUIView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift index 1af0423..3773c47 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardUIView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift @@ -38,15 +38,8 @@ let hostedView = hostingController.view! hostingController.safeAreaRegions = [] hostedView.backgroundColor = .clear - hostedView.translatesAutoresizingMaskIntoConstraints = false addSubview(hostedView) - - NSLayoutConstraint.activate([ - hostedView.topAnchor.constraint(equalTo: topAnchor), - hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), - hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), - hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + hostedView.pinToSuperview() hostingController.rootView = makeRootView() } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/MainKeyboardView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/NumbersKeyboardView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboard/OperationsKeyboardView.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index 049b967..fc63596 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -112,15 +112,8 @@ hostingController.safeAreaRegions = [] } hostedView.backgroundColor = .clear - hostedView.translatesAutoresizingMaskIntoConstraints = false addSubview(hostedView) - - NSLayoutConstraint.activate([ - hostedView.topAnchor.constraint(equalTo: topAnchor), - hostedView.leadingAnchor.constraint(equalTo: leadingAnchor), - hostedView.trailingAnchor.constraint(equalTo: trailingAnchor), - hostedView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) + hostedView.pinToSuperview() updateRootView() } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift new file mode 100644 index 0000000..6499f3b --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift @@ -0,0 +1,27 @@ +import Foundation + +#if os(iOS) + import UIKit + typealias MTView = UIView +#elseif os(macOS) + import AppKit + typealias MTView = NSView +#endif + +extension MTView { + func pinToSuperview() { + pinToSuperview(top: 0, leading: 0, bottom: 0, trailing: 0) + } + + func pinToSuperview(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { + guard let superview else { return } + + translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + topAnchor.constraint(equalTo: superview.topAnchor, constant: top), + leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading), + trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -trailing), + bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom), + ]) + } +} From 3807c81a9e16533b63441b875387abf42de783dd Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 01:43:43 +0000 Subject: [PATCH 077/133] nit --- .../Keyboards/LettersKeyboardView.swift | 3 ++- .../MTView+AutoLayout.swift | 24 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift index 68508f9..c313d31 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift @@ -50,7 +50,8 @@ struct LettersKeyboardView: View { VStack(spacing: 0) { letterRow(topRow, horizontalInset: 0, unitWidth: unitWidth, rowHeight: rowHeight) - letterRow(middleRow, horizontalInset: unitWidth / 2, unitWidth: unitWidth, rowHeight: rowHeight) + letterRow( + middleRow, horizontalInset: unitWidth / 2, unitWidth: unitWidth, rowHeight: rowHeight) bottomLetterRow(unitWidth: unitWidth, rowHeight: rowHeight) greekRowView(unitWidth: unitWidth, rowHeight: rowHeight) } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift index 6499f3b..23d218b 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift @@ -3,25 +3,37 @@ import Foundation #if os(iOS) import UIKit typealias MTView = UIView + typealias MTViewEdgeInsets = UIEdgeInsets #elseif os(macOS) import AppKit typealias MTView = NSView + typealias MTViewEdgeInsets = NSEdgeInsets #endif extension MTView { func pinToSuperview() { - pinToSuperview(top: 0, leading: 0, bottom: 0, trailing: 0) + pinToSuperview(insets: .zero) } - func pinToSuperview(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { + func pinToSuperview(insets: MTViewEdgeInsets) { guard let superview else { return } translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - topAnchor.constraint(equalTo: superview.topAnchor, constant: top), - leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading), - trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -trailing), - bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom), + topAnchor.constraint(equalTo: superview.topAnchor, constant: insets.top), + leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: insets.left), + trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -insets.right), + bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -insets.bottom), ]) } } + +extension MTViewEdgeInsets { + static var zero: Self { + #if os(iOS) + .zero + #else + NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + #endif + } +} From 62f9f4c18dac98be2cf89c96fcbf36fe72da051e Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 02:02:03 +0000 Subject: [PATCH 078/133] Address reviews --- .../Keyboards/LettersKeyboardUIView.swift | 7 +++++++ .../Keyboards/LettersKeyboardView.swift | 5 +++-- .../MTMathKeyboardSwiftUIRootView.swift | 6 ++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift index 7c6b0a4..fbf3086 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift @@ -5,6 +5,7 @@ final class LettersKeyboardUIView: UIView, KeyboardConfigurable { private var keyboardState = KeyboardState() + private var isLowercase = true private weak var textInput: (any UIView & UIKeyInput)? private lazy var hostingController = UIHostingController(rootView: makeRootView()) @@ -44,6 +45,12 @@ private func makeRootView() -> LettersKeyboardView { LettersKeyboardView( state: keyboardState, + isLowercase: isLowercase, + onShift: { [weak self] in + guard let self else { return } + self.isLowercase.toggle() + self.hostingController.rootView = self.makeRootView() + }, onAction: { [weak self] action in self?.handle(action) } ) } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift index c313d31..2540f91 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift @@ -3,8 +3,9 @@ import SwiftUI struct LettersKeyboardView: View { let state: KeyboardState + let isLowercase: Bool + let onShift: () -> Void let onAction: (KeyboardAction) -> Void - @State private var isLowercase = true private var topRow: [String] { makeLetterRow(["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]) @@ -142,7 +143,7 @@ struct LettersKeyboardView: View { private var shiftCell: KeyboardCell { .image( imageName: "Shift", - action: { isLowercase.toggle() }, + action: onShift, enabled: true, accessibilityLabel: "Shift", pressedAsset: "Keyboard-grey-pressed" diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index fc63596..bf45381 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -84,8 +84,10 @@ } public func finishedEditing(_ label: (any UIView & UIKeyInput)!) { - textInput = nil - updateRootView() + if textInput === label { + textInput = nil + updateRootView() + } } private func updateState(_ update: (inout KeyboardState) -> Void) { From 887f87c81f276f4312f8186431ecbe8a6c02c3d7 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 02:07:34 +0000 Subject: [PATCH 079/133] Rename to setTextInput --- .../Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift | 2 +- .../MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift | 4 ++-- .../MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift | 2 +- .../MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index 74f8437..b231085 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -45,7 +45,7 @@ fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { for keyboard in keyboards.values { - keyboard.setEditingTarget(editingTarget) + keyboard.setTextInput(editingTarget) keyboard.applyKeyboardState(state) } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift index 59cca76..ac51638 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift @@ -11,12 +11,12 @@ import UIKit protocol KeyboardConfigurable: AnyObject { - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) + func setTextInput(_ textView: (any UIView & UIKeyInput)?) func applyKeyboardState(_ state: KeyboardState) } extension MTKeyboard: KeyboardConfigurable { - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + func setTextInput(_ textView: (any UIView & UIKeyInput)?) { self.textView = textView } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift index fbf3086..4dc1b03 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift @@ -19,7 +19,7 @@ fatalError("init(coder:) has not been implemented") } - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + func setTextInput(_ textView: (any UIView & UIKeyInput)?) { textInput = textView } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift index 3773c47..1139554 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift @@ -23,7 +23,7 @@ fatalError("init(coder:) has not been implemented") } - func setEditingTarget(_ textView: (any UIView & UIKeyInput)?) { + func setTextInput(_ textView: (any UIView & UIKeyInput)?) { textInput = textView } From f7d1dec30f7f98ba7849e6686ea73b5b2ebf2c67 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 02:16:29 +0000 Subject: [PATCH 080/133] Fix recursive --- .../Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift index 23d218b..456a8ef 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift @@ -29,11 +29,9 @@ extension MTView { } extension MTViewEdgeInsets { +#if os(macOS) static var zero: Self { - #if os(iOS) - .zero - #else NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - #endif } +#endif } From 8b8157ecdf8e9fa59a8c09c609af6cf6c76a893c Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 09:40:22 +0000 Subject: [PATCH 081/133] First and second tab are almost identical --- .../MathKeyboardSwiftUI/KeyboardTab.swift | 2 +- .../Keyboards/KeyButton.swift | 6 ++-- .../Keyboards/KeyboardCell.swift | 33 +++++++++++++++---- .../Keyboards/MainKeyboardView.swift | 9 +++-- .../Keyboards/NumbersKeyboardView.swift | 18 +++++++--- .../Keyboards/OperationsKeyboardView.swift | 6 ++-- 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift index d80e94e..b93c3c6 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift @@ -29,7 +29,7 @@ extension KeyboardTab { var nibName: String { switch self { case .numbers: return "MTKeyboard" - case .legacyNumbers: return "MTKeyboard" + case .legacyNumbers: return "MTKeyboardTab3" case .operations: return "MTKeyboardTab2" case .functions: return "MTKeyboardTab3" case .letters: return "MTKeyboardTab4" diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift index 998e2c2..a14f9ef 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift @@ -27,12 +27,12 @@ struct KeyButton: View { Text(text.value) .font(.custom(text.fontName, size: text.fontSize)) .foregroundColor(textColor(for: text.tone)) + .padding(cell.padding) case .image(let image): mtMathImage(image.name) .renderingMode(.original) - .resizable() - .scaledToFit() - .padding(image.padding) + .scaledToFill() + .padding(cell.padding) } } } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardCell.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardCell.swift index b85af21..1c9c5cc 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardCell.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardCell.swift @@ -6,6 +6,7 @@ // import Foundation +import SwiftUI struct KeyboardCell: Identifiable { enum Content { @@ -22,7 +23,6 @@ struct KeyboardCell: Identifiable { struct ImageContent { let name: String - let padding: Double } enum TextTone { @@ -38,7 +38,8 @@ struct KeyboardCell: Identifiable { let accessibilityLabel: String let pressedAsset: String? let overlayAsset: String? - + var padding: EdgeInsets + static func text( label: String, tone: TextTone, @@ -48,7 +49,8 @@ struct KeyboardCell: Identifiable { enabled: Bool, accessibilityLabel: String? = nil, pressedAsset: String? = nil, - overlayAsset: String? = nil + overlayAsset: String? = nil, + padding: EdgeInsets = .zero, ) -> KeyboardCell { KeyboardCell( content: .text( @@ -63,7 +65,8 @@ struct KeyboardCell: Identifiable { enabled: enabled, accessibilityLabel: accessibilityLabel ?? label, pressedAsset: pressedAsset, - overlayAsset: overlayAsset + overlayAsset: overlayAsset, + padding: padding ) } @@ -73,15 +76,31 @@ struct KeyboardCell: Identifiable { enabled: Bool, accessibilityLabel: String, pressedAsset: String? = nil, - overlayAsset: String? = nil + overlayAsset: String? = nil, + padding: EdgeInsets = .zero, ) -> KeyboardCell { KeyboardCell( - content: .image(ImageContent(name: imageName, padding: 8)), + content: .image(ImageContent(name: imageName)), action: action, enabled: enabled, accessibilityLabel: accessibilityLabel, pressedAsset: pressedAsset, - overlayAsset: overlayAsset + overlayAsset: overlayAsset, + padding: padding ) } } + +extension EdgeInsets { + static var zero: EdgeInsets { + EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) + } + + static func bottom(_ length: CGFloat) -> EdgeInsets { + EdgeInsets(top: 0, leading: 0, bottom: length, trailing: 0) + } + + static func top(_ length: CGFloat) -> EdgeInsets { + EdgeInsets(top: length, leading: 0, bottom: 0, trailing: 0) + } +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift index 0e27a8d..94cc909 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift @@ -57,11 +57,11 @@ extension MainKeyboardView { .text( label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName, action: { onAction(.insertText("x")) }, enabled: state.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), + pressedAsset: "Keyboard-marine-pressed", padding: .bottom(10)), .text( label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName, action: { onAction(.insertText("y")) }, enabled: state.variablesAllowed, - pressedAsset: "Keyboard-marine-pressed"), + pressedAsset: "Keyboard-marine-pressed", padding: .bottom(10)), .image( imageName: "Fraction", action: { onAction(.insertText(MTSymbolFractionSlash)) }, @@ -84,6 +84,7 @@ extension MainKeyboardView { private var enterCell: KeyboardCell { KeyboardCell.text( label: "Enter", tone: .light, + fontName: "Helvetica Neue Light", action: { onAction(.insertText("\n")) }, enabled: true, pressedAsset: "Keyboard-grey-pressed") } @@ -91,7 +92,9 @@ extension MainKeyboardView { KeyboardCell.image( imageName: "Keyboard Down", action: { onAction(.dismiss) }, enabled: true, accessibilityLabel: "Dismiss keyboard", - pressedAsset: "Keyboard-grey-pressed") + pressedAsset: "Keyboard-grey-pressed", + padding: .bottom(5) + ) } } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift index 0d36141..878ae0a 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift @@ -64,16 +64,24 @@ private func makeNumbersGrid( let operatorItems: [KeyboardCell] = [ .text( label: "÷", tone: .dark, action: { onAction(.insertText("÷")) }, - enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed", + padding: .bottom(5) + ), .text( label: "×", tone: .dark, action: { onAction(.insertText("×")) }, - enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed", + padding: .bottom(7) + ), .text( - label: "-", tone: .dark, action: { onAction(.insertText("-")) }, - enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + label: "-", tone: .dark, fontSize: 25, action: { onAction(.insertText("-")) }, + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed", + padding: .bottom(9) + ), .text( label: "+", tone: .dark, action: { onAction(.insertText("+")) }, - enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed"), + enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed", + padding: .bottom(5) + ), ] return [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems] } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift index 3bd03de..c8c5a15 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift @@ -53,8 +53,10 @@ private func makeOperationsGrid( label: "≤", tone: .dark, action: { onAction(.insertText("≤")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), .text( - label: "|□|", tone: .dark, action: { onAction(.insertText("||")) }, enabled: true, - accessibilityLabel: "Absolute value", pressedAsset: "Keyboard-orange-pressed"), + label: "|□|", tone: .dark, fontSize: 24, action: { onAction(.insertText("||")) }, + enabled: true, accessibilityLabel: "Absolute value", + pressedAsset: "Keyboard-orange-pressed" + ), .text( label: ":", tone: .dark, action: { onAction(.insertText(":")) }, enabled: true, pressedAsset: "Keyboard-orange-pressed"), From 08fd6f33649a926d07b5ffee39a174b992418672 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 09:51:07 +0000 Subject: [PATCH 082/133] Fix Tab4 --- .../Sources/MathKeyboardSwiftUI/KeyboardTab.swift | 2 +- .../MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift index b93c3c6..008eb9e 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift @@ -29,7 +29,7 @@ extension KeyboardTab { var nibName: String { switch self { case .numbers: return "MTKeyboard" - case .legacyNumbers: return "MTKeyboardTab3" + case .legacyNumbers: return "MTKeyboardTab4" case .operations: return "MTKeyboardTab2" case .functions: return "MTKeyboardTab3" case .letters: return "MTKeyboardTab4" diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift index 2540f91..f786f40 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift @@ -122,7 +122,7 @@ struct LettersKeyboardView: View { label: label, tone: .dark, action: { onAction(.insertText(label)) }, - enabled: state.variablesAllowed, + enabled: true, pressedAsset: "Keyboard-azure-pressed" ) ) @@ -134,7 +134,7 @@ struct LettersKeyboardView: View { tone: .dark, fontName: "CourierNewPS-ItalicMT", action: { onAction(.insertText(key.label)) }, - enabled: state.variablesAllowed, + enabled: true, accessibilityLabel: key.accessibilityLabel, pressedAsset: "Keyboard-azure-pressed" ) From db2bfa9254b72422f8c863b37dfdcf2ab163dcbb Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 09:51:34 +0000 Subject: [PATCH 083/133] Fix functions keyboard key layout to match legacy tab --- .../Keyboards/FunctionsKeyboardView.swift | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift index bb6df9d..be9830c 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift @@ -62,9 +62,14 @@ private func makeFunctionsGrid( .text( label: "cot", tone: .dark, action: { onAction(.insertText("cot")) }, enabled: true, pressedAsset: "Keyboard-green-pressed"), - .text( - label: "π", tone: .dark, fontName: "TimesNewRomanPSMT", - action: { onAction(.insertText("π")) }, enabled: true, + .image( + imageName: "Log with base", + action: { + onAction(.insertText("log")) + onAction(.insertText("_")) + }, + enabled: true, + accessibilityLabel: "Log with base", pressedAsset: "Keyboard-green-pressed"), .image( imageName: "Sqrt with Power", @@ -85,13 +90,13 @@ private func makeFunctionsGrid( action: { onAction(.insertText("π")) }, enabled: true, pressedAsset: "Keyboard-green-pressed"), .text( - label: "", tone: .dark, - action: {}, enabled: false, - accessibilityLabel: "Unused"), + label: "∠", tone: .dark, fontName: "Apple SD Gothic Neo", + action: { onAction(.insertText("∠")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), .text( - label: "", tone: .dark, - action: {}, enabled: false, - accessibilityLabel: "Unused"), + label: "°", tone: .dark, fontName: "HelveticaNeue-ThinItalic", + action: { onAction(.insertText("°")) }, enabled: true, + pressedAsset: "Keyboard-green-pressed"), ] return [trigLeftItems, trigMiddleItems, trigRightItems, constantsItems] From 922bba10974e04bd0accdbf07b9e0c2c924fa65b Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 10:17:51 +0000 Subject: [PATCH 084/133] Route keyboard actions via root and use typed SwiftUI keyboard views --- .../KeyboardContainerView.swift | 103 ++++-------------- .../MathKeyboardSwiftUI/KeyboardState.swift | 1 + .../MathKeyboardSwiftUI/KeyboardTab.swift | 29 +---- .../Keyboards/FunctionsKeyboardView.swift | 22 ++-- .../Keyboards/KeyboardAction.swift | 1 + .../Keyboards/KeyboardConfigurable.swift | 35 ------ .../Keyboards/LettersKeyboardUIView.swift | 86 --------------- .../Keyboards/MainKeyboardUIView.swift | 79 -------------- .../Keyboards/NumbersKeyboardView.swift | 22 ++-- .../Keyboards/OperationsKeyboardView.swift | 22 ++-- .../MTMathKeyboardSwiftUIRootView.swift | 19 +++- .../MathKeyboardRootView.swift | 33 ++---- 12 files changed, 96 insertions(+), 356 deletions(-) delete mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift delete mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift delete mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index b231085..77c5154 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -7,91 +7,34 @@ #if os(iOS) - import MathEditor - import MathKeyboard import SwiftUI - import UIKit - struct KeyboardContainerView: UIViewRepresentable { + struct KeyboardContainerView: View { let state: KeyboardState - weak var textInput: (any UIView & UIKeyInput)? - - func makeUIView(context: Context) -> KeyboardContainerUIView { - let view = KeyboardContainerUIView() - view.sync( - state: state, - editingTarget: textInput - ) - return view - } - - func updateUIView(_ uiView: KeyboardContainerUIView, context: Context) { - uiView.sync( - state: state, - editingTarget: textInput - ) - } - } - - final class KeyboardContainerUIView: UIView { - private weak var currentKeyboard: UIView? - private lazy var keyboards: [KeyboardTab: (UIView & KeyboardConfigurable)] = [ - .numbers: makeNumbersKeyboard(), - .legacyNumbers: makeKeyboard(named: KeyboardTab.legacyNumbers.nibName), - .operations: makeOperationsKeyboard(), - .functions: makeFunctionsKeyboard(), - .letters: makeLettersKeyboard(), - ] - - fileprivate func sync(state: KeyboardState, editingTarget: (any UIView & UIKeyInput)?) { - for keyboard in keyboards.values { - keyboard.setTextInput(editingTarget) - keyboard.applyKeyboardState(state) - } - - display(keyboard: keyboards[state.currentTab]!) - } - - fileprivate func display(keyboard: UIView) { - guard currentKeyboard !== keyboard else { return } - - currentKeyboard?.removeFromSuperview() - currentKeyboard = keyboard - addSubview(keyboard) - keyboard.pinToSuperview() - } - - private func makeNumbersKeyboard() -> UIView & KeyboardConfigurable { - MainKeyboardUIView { state, onAction in - numbersKeyboardView(state: state, onAction: onAction) - } - } - - private func makeOperationsKeyboard() -> UIView & KeyboardConfigurable { - MainKeyboardUIView { state, onAction in - operationsKeyboardView(state: state, onAction: onAction) - } - } - - private func makeFunctionsKeyboard() -> UIView & KeyboardConfigurable { - MainKeyboardUIView { state, onAction in - functionsKeyboardView(state: state, onAction: onAction) + let onAction: (KeyboardAction) -> Void + + var body: some View { + keyboardView(for: state.currentTab) + } + + @ViewBuilder + private func keyboardView(for tab: KeyboardTab) -> some View { + switch tab { + case .numbers: + NumbersKeyboardView(state: state, onAction: onAction) + case .operations: + OperationsKeyboardView(state: state, onAction: onAction) + case .functions: + FunctionsKeyboardView(state: state, onAction: onAction) + case .letters: + LettersKeyboardView( + state: state, + isLowercase: state.isLowercase, + onShift: { onAction(.toggleShift) }, + onAction: onAction + ) } } - - private func makeLettersKeyboard() -> UIView & KeyboardConfigurable { - LettersKeyboardUIView() - } - - private func makeKeyboard(named nibName: String) -> UIView & KeyboardConfigurable { - let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() - let keyboard = UINib(nibName: nibName, bundle: bundle) - .instantiate(withOwner: nil, options: nil) - .compactMap { $0 as? MTKeyboard } - .first! - keyboard.translatesAutoresizingMaskIntoConstraints = false - return keyboard - } } #endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift index c5327fc..c65492a 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardState.swift @@ -7,6 +7,7 @@ struct KeyboardState: Equatable { var currentTab: KeyboardTab = .numbers + var isLowercase = true var equalsAllowed = true var fractionsAllowed = true var variablesAllowed = true diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift index b93c3c6..f197cdf 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardTab.swift @@ -7,7 +7,6 @@ enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable { case numbers - case legacyNumbers case operations case functions case letters @@ -16,30 +15,12 @@ enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable { } extension KeyboardTab { - var imageNames: (normal: String, selected: String)? { + var imageNames: (normal: String, selected: String) { switch self { - case .numbers: return ("Numbers Symbol wbg", "Number Symbol") - case .legacyNumbers: return nil - case .operations: return ("Operations Symbol wbg", "Operations Symbol") - case .functions: return ("Functions Symbol wbg", "Functions Symbol") - case .letters: return ("Letter Symbol wbg", "Letter Symbol") - } - } - - var nibName: String { - switch self { - case .numbers: return "MTKeyboard" - case .legacyNumbers: return "MTKeyboardTab3" - case .operations: return "MTKeyboardTab2" - case .functions: return "MTKeyboardTab3" - case .letters: return "MTKeyboardTab4" - } - } - - var title: String? { - switch self { - case .legacyNumbers: return "Old" - default: return nil + case .numbers: ("Numbers Symbol wbg", "Number Symbol") + case .operations: ("Operations Symbol wbg", "Operations Symbol") + case .functions: ("Functions Symbol wbg", "Functions Symbol") + case .letters: ("Letter Symbol wbg", "Letter Symbol") } } } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift index be9830c..813ebe8 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift @@ -2,16 +2,18 @@ import Foundation import MathEditor import SwiftUI -func functionsKeyboardView( - state: KeyboardState, - onAction: @escaping (KeyboardAction) -> Void -) -> MainKeyboardView { - MainKeyboardView( - backgroundImageName: "Functions Keyboard", - middleColumns: makeFunctionsGrid(state: state, onAction: onAction), - state: state, - onAction: onAction - ) +struct FunctionsKeyboardView: View { + let state: KeyboardState + let onAction: (KeyboardAction) -> Void + + var body: some View { + MainKeyboardView( + backgroundImageName: "Functions Keyboard", + middleColumns: makeFunctionsGrid(state: state, onAction: onAction), + state: state, + onAction: onAction + ) + } } private func makeFunctionsGrid( diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardAction.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardAction.swift index fe2ea70..7172a21 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardAction.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardAction.swift @@ -2,4 +2,5 @@ enum KeyboardAction { case insertText(String) case backspace case dismiss + case toggleShift } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift deleted file mode 100644 index ac51638..0000000 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardConfigurable.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// NumbersKeyboardUIView.swift -// MathKeyboardSwiftUI -// -// Created by Madiyar Aitbayev on 23/03/2026. -// - -#if os(iOS) - - import MathKeyboard - import UIKit - - protocol KeyboardConfigurable: AnyObject { - func setTextInput(_ textView: (any UIView & UIKeyInput)?) - func applyKeyboardState(_ state: KeyboardState) - } - - extension MTKeyboard: KeyboardConfigurable { - func setTextInput(_ textView: (any UIView & UIKeyInput)?) { - self.textView = textView - } - - func applyKeyboardState(_ state: KeyboardState) { - setNumbersState(state.numbersAllowed) - setOperatorState(state.operatorsAllowed) - setVariablesState(state.variablesAllowed) - setFractionState(state.fractionsAllowed) - setEqualsState(state.equalsAllowed) - setExponentState(state.exponentHighlighted) - setSquareRootState(state.squareRootHighlighted) - setRadicalState(state.radicalHighlighted) - } - } - -#endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift deleted file mode 100644 index 4dc1b03..0000000 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardUIView.swift +++ /dev/null @@ -1,86 +0,0 @@ -#if os(iOS) - - import SwiftUI - import UIKit - - final class LettersKeyboardUIView: UIView, KeyboardConfigurable { - private var keyboardState = KeyboardState() - private var isLowercase = true - private weak var textInput: (any UIView & UIKeyInput)? - private lazy var hostingController = UIHostingController(rootView: makeRootView()) - - override init(frame: CGRect) { - super.init(frame: frame) - commonInit() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setTextInput(_ textView: (any UIView & UIKeyInput)?) { - textInput = textView - } - - func applyKeyboardState(_ state: KeyboardState) { - updateState { $0 = state } - } - - private func commonInit() { - translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .white - - let hostedView = hostingController.view! - if #available(iOS 16.4, *) { - hostingController.safeAreaRegions = [] - } - hostedView.backgroundColor = .clear - addSubview(hostedView) - hostedView.pinToSuperview() - - hostingController.rootView = makeRootView() - } - - private func makeRootView() -> LettersKeyboardView { - LettersKeyboardView( - state: keyboardState, - isLowercase: isLowercase, - onShift: { [weak self] in - guard let self else { return } - self.isLowercase.toggle() - self.hostingController.rootView = self.makeRootView() - }, - onAction: { [weak self] action in self?.handle(action) } - ) - } - - private func handle(_ action: KeyboardAction) { - playClickForCustomKeyTap() - - switch action { - case .insertText(let text): - textInput?.insertText(text) - case .backspace: - textInput?.deleteBackward() - case .dismiss: - textInput?.resignFirstResponder() - } - } - - private func playClickForCustomKeyTap() { - UIDevice.current.playInputClick() - } - - private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { - DispatchQueue.main.async { [weak self] in - guard let self else { return } - let previousState = self.keyboardState - update(&self.keyboardState) - guard self.keyboardState != previousState else { return } - self.hostingController.rootView = self.makeRootView() - } - } - } - -#endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift deleted file mode 100644 index 1139554..0000000 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardUIView.swift +++ /dev/null @@ -1,79 +0,0 @@ -#if os(iOS) - - import SwiftUI - import UIKit - - final class MainKeyboardUIView: UIView, KeyboardConfigurable { - typealias RootViewBuilder = (KeyboardState, @escaping (KeyboardAction) -> Void) -> - MainKeyboardView - - private var keyboardState = KeyboardState() - private weak var textInput: (any UIView & UIKeyInput)? - private let rootViewBuilder: RootViewBuilder - private lazy var hostingController = UIHostingController(rootView: makeRootView()) - - init(rootViewBuilder: @escaping RootViewBuilder) { - self.rootViewBuilder = rootViewBuilder - super.init(frame: .zero) - commonInit() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setTextInput(_ textView: (any UIView & UIKeyInput)?) { - textInput = textView - } - - func applyKeyboardState(_ state: KeyboardState) { - updateState { $0 = state } - } - - private func commonInit() { - translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .white - - let hostedView = hostingController.view! - hostingController.safeAreaRegions = [] - hostedView.backgroundColor = .clear - addSubview(hostedView) - hostedView.pinToSuperview() - - hostingController.rootView = makeRootView() - } - - private func makeRootView() -> MainKeyboardView { - rootViewBuilder(keyboardState) { [weak self] action in self?.handle(action) } - } - - private func handle(_ action: KeyboardAction) { - playClickForCustomKeyTap() - - switch action { - case .insertText(let text): - textInput?.insertText(text) - case .backspace: - textInput?.deleteBackward() - case .dismiss: - textInput?.resignFirstResponder() - } - } - - private func playClickForCustomKeyTap() { - UIDevice.current.playInputClick() - } - - private func updateState(_ update: @escaping (inout KeyboardState) -> Void) { - DispatchQueue.main.async { [weak self] in - guard let self else { return } - let previousState = self.keyboardState - update(&self.keyboardState) - guard self.keyboardState != previousState else { return } - self.hostingController.rootView = self.makeRootView() - } - } - } - -#endif // os(iOS) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift index 878ae0a..5f9bbaf 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/NumbersKeyboardView.swift @@ -1,16 +1,18 @@ import Foundation import SwiftUI -func numbersKeyboardView( - state: KeyboardState, - onAction: @escaping (KeyboardAction) -> Void -) -> MainKeyboardView { - MainKeyboardView( - backgroundImageName: "Numbers Keyboard", - middleColumns: makeNumbersGrid(state: state, onAction: onAction), - state: state, - onAction: onAction - ) +struct NumbersKeyboardView: View { + let state: KeyboardState + let onAction: (KeyboardAction) -> Void + + var body: some View { + MainKeyboardView( + backgroundImageName: "Numbers Keyboard", + middleColumns: makeNumbersGrid(state: state, onAction: onAction), + state: state, + onAction: onAction + ) + } } private func makeNumbersGrid( diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift index c8c5a15..5aa3c1f 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/OperationsKeyboardView.swift @@ -1,16 +1,18 @@ import Foundation import SwiftUI -func operationsKeyboardView( - state: KeyboardState, - onAction: @escaping (KeyboardAction) -> Void -) -> MainKeyboardView { - MainKeyboardView( - backgroundImageName: "Operations Keyboard", - middleColumns: makeOperationsGrid(state: state, onAction: onAction), - state: state, - onAction: onAction - ) +struct OperationsKeyboardView: View { + let state: KeyboardState + let onAction: (KeyboardAction) -> Void + + var body: some View { + MainKeyboardView( + backgroundImageName: "Operations Keyboard", + middleColumns: makeOperationsGrid(state: state, onAction: onAction), + state: state, + onAction: onAction + ) + } } private func makeOperationsGrid( diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index bf45381..65b2430 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -98,9 +98,11 @@ private func makeRootView() -> MathKeyboardRootView { MathKeyboardRootView( state: state, - textInput: textInput, onTabSelected: { [weak self] tab in self?.updateState { $0.currentTab = tab } + }, + onAction: { [weak self] action in + self?.handleKeyboardAction(action) } ) } @@ -123,6 +125,21 @@ private func updateRootView() { hostingController.rootView = makeRootView() } + + private func handleKeyboardAction(_ action: KeyboardAction) { + UIDevice.current.playInputClick() + + switch action { + case .insertText(let text): + textInput?.insertText(text) + case .backspace: + textInput?.deleteBackward() + case .dismiss: + textInput?.resignFirstResponder() + case .toggleShift: + updateState { $0.isLowercase.toggle() } + } + } } #endif diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift index 318a907..7b7d75f 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -12,8 +12,8 @@ import SwiftUI public struct MathKeyboardRootView: View { let state: KeyboardState - weak var textInput: (any UIView & UIKeyInput)? let onTabSelected: (KeyboardTab) -> Void + let onAction: (KeyboardAction) -> Void public var body: some View { GeometryReader { proxy in @@ -27,20 +27,13 @@ import SwiftUI Button { onTabSelected(tab) } label: { - if let image = tabImage(for: tab) { - Image(uiImage: image) - .renderingMode(.original) - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(.horizontal, 8) - .padding(.vertical, 6) - } else { - Text(tab.title ?? "") - .font(.system(size: 14, weight: state.currentTab == tab ? .semibold : .regular)) - .foregroundColor(.black) - .frame(maxWidth: .infinity, maxHeight: .infinity) - } + Image(uiImage: tabImage(for: tab)) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal, 8) + .padding(.vertical, 6) } .buttonStyle(.plain) .background(Color(white: 0.768627451)) @@ -50,7 +43,7 @@ import SwiftUI KeyboardContainerView( state: state, - textInput: textInput + onAction: onAction ) .frame(height: keyboardHeight) } @@ -59,16 +52,14 @@ import SwiftUI } } - private func tabImage(for tab: KeyboardTab) -> UIImage? { - guard let names = tab.imageNames else { - return nil - } + private func tabImage(for tab: KeyboardTab) -> UIImage { + let names = tab.imageNames let name = state.currentTab == tab ? names.selected : names.normal return UIImage( named: name, in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), compatibleWith: nil - ) + ) ?? UIImage() } } From fd266c40492eab1ecb921ea319dcabca2e250f97 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 12:34:54 +0000 Subject: [PATCH 085/133] Remove MathKeyboard dependency from MathKeyboardSwiftUI --- .../MathEditorSwiftUIExample.xcscheme | 78 ++++++++++++++++++ MathKeyboardSwiftUI/Package.swift | 6 +- .../Keyboards/KeyButton.swift | 6 +- .../Keyboards/LettersKeyboardView.swift | 2 +- .../Keyboards/MTMathKeyboardCompat.swift | 52 +++--------- .../Keyboards/MainKeyboardView.swift | 2 +- .../MTMathKeyboardSwiftUIRootView.swift | 1 - .../MathKeyboardRootView.swift | 3 +- .../Contents.json | 23 ++++++ .../left arrow disabled 1x.png | Bin 0 -> 532 bytes .../left arrow disabled 2x.png | Bin 0 -> 999 bytes .../left arrow disabled 3x.png | Bin 0 -> 1420 bytes .../back-arrow.imageset/Contents.json | 23 ++++++ .../back-arrow.imageset/left arrow 1x.png | Bin 0 -> 575 bytes .../back-arrow.imageset/left arrow 2x.png | Bin 0 -> 1036 bytes .../back-arrow.imageset/left arrow 3x.png | Bin 0 -> 1502 bytes .../Contents.json | 21 +++++ .../blue-pressed.png | Bin 0 -> 171 bytes .../Contents.json | 23 ++++++ .../right arrow disabled 1x.png | Bin 0 -> 520 bytes .../right arrow disabled 2x.png | Bin 0 -> 944 bytes .../right arrow disabled 3x.png | Bin 0 -> 1364 bytes .../front-arrow.imageset/Contents.json | 23 ++++++ .../front-arrow.imageset/right arrow 1x.png | Bin 0 -> 553 bytes .../front-arrow.imageset/right arrow 2x.png | Bin 0 -> 1002 bytes .../front-arrow.imageset/right arrow 3x.png | Bin 0 -> 1465 bytes .../Contents.json | 21 +++++ .../grey-button-disabled.png | Bin 0 -> 974 bytes .../Contents.json | 21 +++++ .../keyboard-grey-pressed.png | Bin 0 -> 169 bytes .../ipad-background.imageset/Contents.json | 22 +++++ .../ipad-keyboard1x.png | Bin 0 -> 2417 bytes .../ipad-keyboard2x.png | Bin 0 -> 5989 bytes .../iphone-background.imageset/Contents.json | 22 +++++ .../keyboard-background1x.png | Bin 0 -> 1224 bytes .../keyboard-background2x.png | Bin 0 -> 2383 bytes .../Contents.json | 21 +++++ .../kb-dark-blue-pressed.png | Bin 0 -> 1064 bytes .../Contents.json | 21 +++++ .../kb-slide-button-pressed.png | Bin 0 -> 1064 bytes .../keyboard-slide-bg1.imageset/Contents.json | 22 +++++ .../keyboard-slide11x.png | Bin 0 -> 354 bytes .../keyboard-slide12x.png | Bin 0 -> 594 bytes .../keyboard-slide-bg2.imageset/Contents.json | 22 +++++ .../keyboard-slide21x.png | Bin 0 -> 351 bytes .../keyboard-slide22x.png | Bin 0 -> 590 bytes .../keyboard-slide-bg3.imageset/Contents.json | 22 +++++ .../keyboard-slide31x.png | Bin 0 -> 466 bytes .../keyboard-slide32x.png | Bin 0 -> 782 bytes .../Contents.json | 22 +++++ .../multiplication1x.png | Bin 0 -> 424 bytes .../multiplication2x-ipad.png | Bin 0 -> 749 bytes .../Contents.json | 22 +++++ .../multiplication1x-iphone.png | Bin 0 -> 386 bytes .../multiplication2x-iphone.png | Bin 0 -> 640 bytes .../Contents.json | 21 +++++ .../grey-button-disabled.png | Bin 0 -> 971 bytes .../Contents.json | 21 +++++ .../keyboard-num-pressed.png | Bin 0 -> 971 bytes .../Contents.json | 21 +++++ .../keyboard-orange-pressed.png | Bin 0 -> 170 bytes .../Backspace Small.pdf | Bin 0 -> 9594 bytes .../Backspace Small.imageset/Contents.json | 12 +++ .../Backspace.imageset/Backspace.pdf | Bin 0 -> 9607 bytes .../Backspace.imageset/Contents.json | 12 +++ .../Exponent.imageset/Contents.json | 12 +++ .../Exponent.imageset/Exponent.pdf | Bin 0 -> 9405 bytes .../Fraction.imageset/Contents.json | 12 +++ .../Fraction.imageset/Fraction.pdf | Bin 0 -> 9413 bytes .../Functions Keyboard.imageset/Contents.json | 12 +++ .../Functions Keyboard.pdf | Bin 0 -> 11396 bytes .../Functions Symbol.imageset/Contents.json | 12 +++ .../Functions Symbol.pdf | Bin 0 -> 10234 bytes .../Keyboard Down.imageset/Contents.json | 12 +++ .../Keyboard Down.imageset/Keyboard Down.pdf | Bin 0 -> 9772 bytes .../Contents.json | 12 +++ .../Keyboard-azure-pressed.pdf | Bin 0 -> 973 bytes .../Contents.json | 12 +++ .../Keyboard-green-pressed.pdf | Bin 0 -> 976 bytes .../Contents.json | 12 +++ .../Keyboard-grey-pressed.pdf | Bin 0 -> 966 bytes .../Contents.json | 12 +++ .../Keyboard-marine-pressed.pdf | Bin 0 -> 972 bytes .../Contents.json | 12 +++ .../Keyboard-orange-pressed.pdf | Bin 0 -> 975 bytes .../Letter Symbol.imageset/Contents.json | 12 +++ .../Letter Symbol.imageset/Letter Symbol.pdf | Bin 0 -> 10990 bytes .../Letters Keyboard.imageset/Contents.json | 12 +++ .../Letters Keyboard.pdf | Bin 0 -> 13764 bytes .../Log Inverted.imageset/Contents.json | 12 +++ .../Log Inverted.imageset/Log Inverted.pdf | Bin 0 -> 10512 bytes .../Log with base.imageset/Contents.json | 12 +++ .../Log with base.imageset/Log with base.pdf | Bin 0 -> 10622 bytes .../Number Symbol.imageset/Contents.json | 12 +++ .../Number Symbol.imageset/Number Symbol.pdf | Bin 0 -> 10518 bytes .../Numbers Keyboard.imageset/Contents.json | 12 +++ .../Numbers Keyboard.pdf | Bin 0 -> 11370 bytes .../Contents.json | 12 +++ .../Operations Keyboard.pdf | Bin 0 -> 11395 bytes .../Operations Symbol.imageset/Contents.json | 12 +++ .../Operations Symbol.pdf | Bin 0 -> 10709 bytes .../Shift.imageset/Contents.json | 12 +++ .../Shift.imageset/Shift.pdf | Bin 0 -> 9430 bytes .../Sqrt Inverted.imageset/Contents.json | 12 +++ .../Sqrt White Fixed.pdf | Bin 0 -> 3989 bytes .../Contents.json | 12 +++ .../Sqrt Power Inverted.pdf | Bin 0 -> 7030 bytes .../Sqrt with Power.imageset/Contents.json | 12 +++ .../Sqrt with Power.pdf | Bin 0 -> 9434 bytes .../Sqrt.imageset/Contents.json | 12 +++ .../Sqrt.imageset/Sqrt.pdf | Bin 0 -> 9498 bytes .../Subscript Inverted.imageset/Contents.json | 12 +++ .../Subscript Inverted.pdf | Bin 0 -> 6975 bytes .../Subscript.imageset/Contents.json | 12 +++ .../Subscript.imageset/Subscript.pdf | Bin 0 -> 9584 bytes .../Contents.json | 12 +++ .../Functions Symbol.pdf | Bin 0 -> 7445 bytes .../Letter Symbol wbg.imageset/Contents.json | 12 +++ .../Letter Symbol.pdf | Bin 0 -> 8197 bytes .../Numbers Symbol wbg.imageset/Contents.json | 12 +++ .../Numbers Symbol.pdf | Bin 0 -> 7735 bytes .../Contents.json | 12 +++ .../Operations Symbol.pdf | Bin 0 -> 7918 bytes .../Resources/lmroman10-bolditalic.otf | Bin 0 -> 118204 bytes 124 files changed, 884 insertions(+), 52 deletions(-) create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf create mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/lmroman10-bolditalic.otf diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme new file mode 100644 index 0000000..f7ab85f --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MathKeyboardSwiftUI/Package.swift b/MathKeyboardSwiftUI/Package.swift index a073ecc..1bab276 100644 --- a/MathKeyboardSwiftUI/Package.swift +++ b/MathKeyboardSwiftUI/Package.swift @@ -19,10 +19,10 @@ let package = Package( .target( name: "MathKeyboardSwiftUI", dependencies: [ - .product(name: "MathKeyboard", package: "MathEditor", condition: .when(platforms: [.iOS])), - .product(name: "MathEditor", package: "MathEditor"), + .product(name: "MathEditor", package: "MathEditor") ], - path: "Sources/MathKeyboardSwiftUI" + path: "Sources/MathKeyboardSwiftUI", + resources: [.process("Resources")], ) ] ) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift index a14f9ef..775094e 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyButton.swift @@ -19,7 +19,7 @@ struct KeyButton: View { ZStack { Rectangle().fill(Color.white.opacity(0.001)) if let overlayAsset = cell.overlayAsset { - mtMathImage(overlayAsset) + Image(overlayAsset, bundle: .module) .resizable() } switch cell.content { @@ -29,7 +29,7 @@ struct KeyButton: View { .foregroundColor(textColor(for: text.tone)) .padding(cell.padding) case .image(let image): - mtMathImage(image.name) + Image(image.name, bundle: .module) .renderingMode(.original) .scaledToFill() .padding(cell.padding) @@ -60,7 +60,7 @@ private struct KeyboardPressStyle: ButtonStyle { ZStack { if configuration.isPressed { if let pressedAsset { - mtMathImage(pressedAsset) + Image(pressedAsset, bundle: .module) .resizable() .opacity(1.0) } else { diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift index f786f40..e5ddaa2 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/LettersKeyboardView.swift @@ -45,7 +45,7 @@ struct LettersKeyboardView: View { let rowHeight = proxy.size.height / 4 ZStack { - mtMathImage("Letters Keyboard") + Image("Letters Keyboard", bundle: .module) .resizable() .frame(width: proxy.size.width, height: proxy.size.height) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift index 7f19926..9e89103 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift @@ -7,49 +7,19 @@ import SwiftUI -#if os(iOS) - - import MathKeyboard - - func mtMathImage(_ name: String) -> Image { - if let image = UIImage( - named: name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), - compatibleWith: nil - ) { - return Image(uiImage: image) - } - return Image(systemName: "questionmark.square.dashed") - } - -#else - - func mtMathImage(_ name: String) -> Image { - Image(systemName: "questionmark.square.dashed") - } - -#endif - enum KeyboardFontRegistry { static let variableFontName: String = { - #if os(iOS) - guard let bundle = MTMathKeyboardRootView.getMathKeyboardResourcesBundle() else { - return "HelveticaNeue" - } - guard - let fontURL = bundle.url(forResource: "lmroman10-bolditalic", withExtension: "otf"), - let provider = CGDataProvider(url: fontURL as CFURL), - let font = CGFont(provider) - else { - return "HelveticaNeue" - } - - let postScriptName = font.postScriptName as String? ?? "HelveticaNeue" - var error: Unmanaged? - CTFontManagerRegisterGraphicsFont(font, &error) - return postScriptName - #else + guard + let fontURL = Bundle.module.url(forResource: "lmroman10-bolditalic", withExtension: "otf"), + let provider = CGDataProvider(url: fontURL as CFURL), + let font = CGFont(provider) + else { return "HelveticaNeue" - #endif + } + + let postScriptName = font.postScriptName as String? ?? "HelveticaNeue" + var error: Unmanaged? + CTFontManagerRegisterGraphicsFont(font, &error) + return postScriptName }() } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift index 94cc909..4313c8f 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift @@ -17,7 +17,7 @@ struct MainKeyboardView: View { let rowHeight = totalHeight / 4 ZStack { - mtMathImage(backgroundImageName) + Image(backgroundImageName, bundle: .module) .resizable() .frame(width: totalWidth, height: totalHeight) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index 65b2430..0f12e73 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -1,7 +1,6 @@ #if os(iOS) import MathEditor - import MathKeyboard import SwiftUI import UIKit diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift index 7b7d75f..445b93b 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -8,7 +8,6 @@ import SwiftUI #if os(iOS) - import MathKeyboard public struct MathKeyboardRootView: View { let state: KeyboardState @@ -57,7 +56,7 @@ import SwiftUI let name = state.currentTab == tab ? names.selected : names.normal return UIImage( named: name, - in: MTMathKeyboardRootView.getMathKeyboardResourcesBundle(), + in: .module, compatibleWith: nil ) ?? UIImage() } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json new file mode 100644 index 0000000..067e4c8 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "left arrow disabled 1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "left arrow disabled 2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "left arrow disabled 3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png new file mode 100644 index 0000000000000000000000000000000000000000..d24a0ee236b07e185b50465ea04de7e1306a1e8d GIT binary patch literal 532 zcmV+v0_**WP)U4X0X&gd;E}?5ni; zGR_H!!Mtg&EW`45LNM>}x)g+dmr932OMLEV-4J<0T%i9S~@GIu# z61DaXzKZ~rD*S7l2?Uis5;+$zhlV&T`l;u#uLe8^9%<&&oqsqarZfNS(Q9WO73L3f W$x0E`UGTX800008bJmeyX zf2!o*$%8jh6h%Zq!CW@cM2seCBr$O^IlO5Y5}IJTx_&hMg=XfkTV4I-y{dYzXR%(d z%{oPPw&i4N z%EPoKW{DSsoaE`VO-o{S@uCb;Xh+N*PH(=CmOk6GAm%7u##)9*>8M@Iz5-IHTg(ZZ z5vh~?RYt91o)=QGM#(lcia9NIpDc@PAGL{j6{i%8l#W`&EbzKG(H56N9b$HHRxGwf z(`TE~VwO0EXH*g?9VNx=;yjM2ObX@1?BPuuQBnGAQ$ox!ye0ax)kR82ZZXI4wpd%L zl|o)I&k8A7+6F_(CFUg@Y`#~yraVjzF|UXQc--uawOxOOZ$}zC3uB&R zzQ@-Z?8Pwt0q-Y#slgg5N6+`N*tHHG_lGr+t0cIF&+uQS`wb>%t@MYGb2sBXpP1Z5 z`Oe^a8m{s&)dKC7iv+u>hv*7>{8{U1xoe#||5Bia|$QMysJ!gu(+@H`%& zv>4*>_oK>sIyHzP%7#ngyi*0Wi0L>~NyDo)F>n=EN>8Hn&sqDpF3N{9r6y2g#B|&g zsa2$HQL`B0EySS$ZHwB)5OvjMncsG{AciO*ewMLq(TW(Ngt)r-9G#ZL5b5-*3_YFJ z#1ISdbtB7ZQ4Fz#-w`RFxGU0YnPRXh zi6PE7_a(@Lsie|2D+x}>Qxrp_)S$w1s$v+}u9&izLCj)$n8oxki|Jt&)59#LhX?!a Vjc%0`aYFzA002ovPDHLkV1ky_%R~SG literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png new file mode 100644 index 0000000000000000000000000000000000000000..fd77cfe43a6bbd26f31211e9c5b665d173154b68 GIT binary patch literal 1420 zcmZ`(YdF&j9G)`Sh~^Q>aX`MYR8P9GAq}4n^`f zbSj6!BB}?Yl#oi1yFxuWJ?HFvIG@gQe$Vs%-uL}IzYp)HSKv!OtPVGXLm&`!fK2pL zFiU}aRTah7f69HVAmubKz+Y7{S*p=|g@+}RL((7+Bho)my7Wi+6~)njLki{`Ns8rg zBT{1^TrL;KN=!(Lib#&ZC8fqyEO{70AW#@UB=~1kE{^vV`v>TqTMb`7Zkg+H4o;W7 zG`wzQ7>7*sF6<7k$Kg}nZwc0Nc4GS)C3`{jtuL3Qrqns6ALQyUPrRMjd1It;3CMO` zzRF$7j>DT2JAaPFr9&>w52N)Di2NZmKP5Z>9sG~syx)<&-c6G)6gs~7u!Sc7>1jkw zG0AY)DgRLig57ZK6n4Eec;*Jvya4IhenQsj%`~N+ep(Hq$;a-6Y$4!*gcuQ#7Z1MZ zLcpLy@caa)E{AvfDW{pW0JF?>+>WyNMG(aT1CN{huEywt4xvN)w%id=LlM0AylyEr zvaP5>Iy|_`Hdu#8K89`}ie!h(nD^rj($20n4hl(P>7=k}(tJ{R`Ng zNkO}a09UZ>xrMS57O-5|_L3j$4;YNwCNr;KwFv$yf+dwVu%?fqL-O;fl!4 z)cY{a&~Lavt#t~st6OXu)M7SQ(!x+>`sSF;iCUn=e0JkwiwF!GUd)XqP*A<1bOhiZt`sme2&a_t%$ZwP--pVb?!f5~Km~augT^^an3TD49cCL=(NYGq>Xtn4(rEwYt z@-v4$jcCSXI7LYG&omX#WCR9?ei#4JTI_AQT?$yZ>0EnXJCd_T5`b1l54ksTXLCV} zHrN3e6GuyMFbzS&%{xvN-38C~@+`%hN9o37=~z~~Qc1WSpd$pft%T1Vh?T|yvi462c@MC@ZD{cuuGU|_YD6NUy|!U{`X;RC_XkHE2#UX@#NUZAVyoW zEkDMiRSLlA7pdVti_0Yil?ZY3>$!LkH`7qJ**vCDjUOv*h6W@W(O#mKT{Ek--b81h zem7!fK$K*%2N;dTR!Qm7?P7q5>c!|iN8h@tt0Q28FM4fZipTkx!m0uRlisxPEQH~| z_W3VoU^tD=pUa9&6X8)ad3E$8)M6FhJ<51*Ooaq>I1ZKne+~3+H?8b9zAtBNHQGZ} PG$#Zg(TR;-$9aDPk11jV literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json new file mode 100644 index 0000000..31fc527 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "left arrow 1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "left arrow 2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "left arrow 3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png new file mode 100644 index 0000000000000000000000000000000000000000..5784e5f18bd8dc31e6017c3568b1983abca773f6 GIT binary patch literal 575 zcmV-F0>J%=P)K~z|U<=DMT96=n$@y{7K#2^w#z%E!?q)TTL?1GXaMVdea z3H}AzNKkwsU^Hhjdmw^$JG(P`2R_xp?#zDonc4Zh zv^t%RVbRE-0dDGE!?b$cY!aqzNMI%~-tVC%V4h&I%!4^$T6l`bWe1FA0vW-Kurz0b zgc-&20^~GyU`n;|0%IZ^$pP~aFAJb(kd!cw@Vb~UYH3o#OyO;DUCEKI>cUK830G=3 zk^<%#-s7K|j?{&j#WMaDFiBJeW>N|{Zfbzc$FKXt9~#avE2j+{b4SI6LzZsmQtDC6;`p4nI$Ui{EYQn zAjIduoA`n(HF1@2@C`>oY)Y!Cf$}>}gcy}fbxM^ztO=1jiR#u(2g0UKayq9d;(iFf z{c+uiQdN{k!om2b&LkRS5(JNQ*)aE`+N61(^rJ1|!l;y?C9I+U{| z(!rsKJ=AKb^*SM>uP1_Q1BD@^u$RLEL+EDR`NF1KuRXu~UN@zC>KdNYPkJW8lj{Hg N002ovPDHLkV1m|>^tb>3 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b77d261c98562ce6cbf59ee93101f583d7ddfcb8 GIT binary patch literal 1036 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 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json new file mode 100644 index 0000000..a6edac4 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "right arrow disabled 1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "right arrow disabled 2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "right arrow disabled 3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png new file mode 100644 index 0000000000000000000000000000000000000000..1d982c98600c32effc094118f28fd9e50519daa2 GIT binary patch literal 520 zcmV+j0{8uiP)R-!_R zNJP*g1Og%$h`aIYS{wzN+>i5dqu>kMotgXFGxIWY@1UwGuSLWE4e(O$F#l2$%n&nu zfIL5}Nh8db2BCeK<}fY^Lbsk7<3lMBx;O9`OBfVjiE`5dRw83-6`ODYpJaAg%D)?9 zRYs?!UiCcI1-B(DN8RVJiN(}JxteemTN16G(-9+VV>vcKZhOw)Gd{)ymzdf^0cbTQ zu!IIe$u_zKmELEzgj;H*K9;LZ>}WC1`r7o?v8!Qx^tpDf;Y;0tK4H$VUw2^C6UYhn zQ2UbB2h2Adv^iKB<|~fd9xeq_;YjF{)+zy0;SfK&2Ihvj7fN#x6Py#~PH5Jv*aW#? zZt)#|Qj+9=xxp!>sfkiy{^DG?nPjEFOz}f#+ANBNncx>5a)A&BGr^^XITNF*Un%`^uXG1VLZ0000< KMNUMnLSTaLsM`<# literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png new file mode 100644 index 0000000000000000000000000000000000000000..62d40e927936832dbc4526656fe75113d0117142 GIT binary patch literal 944 zcmV;h15f;kP)tGMGYR8WC3RKWIlj6lN4eg?OBv!8C08G_gL9c;7a?bZw~beITF!HKzT0@cK%V53 zSnwH~*IBPq>L+;`ZytYkLCITu3vcP9*D3w%Z{Z!hP&n_>)YueH2{}#oI<>0BCN62X z*Qr@XH*gWJRb0a$wfl>Oob#2}@eH-aEO=Y&JI&CF7^2U4xAr0yv?PXjq~6oOeASv5 zVz#=b@(9tQ7-A}aU&Z6nR>crK&WB<`FHXy1;4H4IFhaC0242K2UM`um2QkFsv@70Nh|c&ruH;J4tC$5J9AC-lT?|~s z#}dz1V-N$E@u`I0XDnif&G<8k=BqJ@Ar8Dgmtejcn;7Cu=L<1Ptjrk25C`mE;+0hc zj8)9qjOx9)|M3UD!sAr~j8V)3+`w#|AY&7=hp%zCZlE!V`3c|PC`GWbh`Ebzk?ha) z#vtYnZlw;|yO>+}F-PED#mw=Y*jKX9qnIOnk6&^o=}pX2ai{#2J5f(!4uq74C6e_b zW+tTkRVrZ*Vjklr{w2i@z)-#5@t@ILYsO zN|9U441cOT_pCxrF*A+!C?_G8n0*cRC?_F@nEm6cYf_8Z*FleR5>kqpsdNmPgH&P; zbkw7qgcM>9by$v{$GR^k9vPt&))zBMDNL`k4l;=uU=lOHBxZm~%m9;^0saG;Hq)$8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11kp)EK~#90?VU?+97PmHkAoAN0D%M&hj&ColvqLnBqR`s6$^wA z{90gv#DYyC9zqC-H;9LLBwzwbNSJu+VR2(6HiO+=GhMfC)pw-Xy6*IusjB-`PkUi9 znIOt$pLYRbZzL%Ik`w?*3VPUZMk@i}^Be zhvWER@ZNDP0e3prP(0p}z(n1Rda3`V1W8~UiNi?LR7#NquphXWvT7=V(k{q;;3#T( zw^5oTux9rdCQMMKB(QJN3bv_eo>C=&)wTlM?p=sCB})RU?>LP&y`@V6+m;^(4tW>i zMFdG(tYE!Vb3~E^w(C3UT|{o8NdmYJxZk^ooJ5oaa0GamMq;F>lE6aQ6U2TGorwMh z?*dLzd5jcY5?Bv)ii%^T8YF?G5~ry&Myf>;n4mLM7$emr32e$+rLrUW+9ZJ;b*uvS zco#A+YLo=F+grsN-|*Bb39QjQjfJzJsaX=3s8d)v5tiB|ZE^C(D!_-Q)l!YQ?*dok-Gl7O^$zA@4$NMUwu@K5W;WNzB1yAx5EC`%ZDW!&J4=|TM}`_e zlIFuA@CpNMCiFbi`7j3F0I~&*r;kVbjLUyBNNq+(#U~5s)v?^&0OA$7_ z3+Y0OlFkF4;1HCi(wd~7fX{IXN>gb`(hpdg9gbEceTzv7Nehy`0)Fu>ViszaGyy&X ze)le9R%(^B34DR|LgA@V(ir#>OB1S~Hc9K4pki~l6l#(*!USczA1)WQNV$drk;A1BUD6fcI|Ai!DMXfZ3AsS_7eGZpG36ic;(NXsh zcuN{#=Rsl@JSB~2yh-hfm!uJDQaj@zX@r{8UgVZELOoD#a!MMZ9w>=iPcvRef|AMc zO&X&nbtZPp%G literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json new file mode 100644 index 0000000..b8aabfb --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "right arrow 1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "right arrow 2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "right arrow 3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png new file mode 100644 index 0000000000000000000000000000000000000000..feb11eaec3f2deb1cd30befcba444f335c1e6673 GIT binary patch literal 553 zcmV+^0@nSBP)XOxJ{Bt}!r2TYxYwO($@=HVETu<_yl&1RGNPjP3FK`WTY`r7%i$S8a#IY7I(H^#6w%mX|eYg`LvhWmJy;#33X zr+%)yOm&Qg`L0jR>m27um~V5Cx48kLV7}lH-j@W4fZ4~B`Ljr+Fdy+8UrRwKgW1Ci z15-6cxiC|_(zkJ#a$u%-gI|##q{B?{PCsG8l>)Pe4>chEXe`Vpe69%s-M}4qNe>*> rF(ojaO7(G&hBm#X=kr5#ly0iu^nW{@Xhpq+00000NkvXXu0mjf2wm%N literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e4789ee8b468aadcfdd26f112f1ebf82ad57978e GIT binary patch literal 1002 zcmV;M1&8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H117}G@K~#90?c7VKRb?25@!!_WEUQzL6cN!Nl7hlPMUkP|Wn4|8 zQ3Q>esH1`?F$=+2PHH03tZZ5o35jBZB&Z-9q(m1g@@Sfu($nB85DrT{dwti%dSFlA z|I4%Y`mXDo$=uwWk@+kt-9W93VmcVbbTEqPU=-8AD5itHVkS|#sF1#5mf*4&jnqbr zM`;-$pzRVLC(Ded+4O}%5;LYc(8CE z^C&Ime^%ph745HT5d#n4DV6k8MHA<-3eT$GxU@Dga5r91__(x2F@$OSB?Y$-wTgk; zuu(YM$xh8;;AXsnn{o%&E(UJEYr=_OHd+uvIPiKMcjXFfMGWEIY7_3y5!R9zxE!xy zUFx9L#1IyLFXNe%AuWm_^t~R>rwnMB7qLNt&1dUk;5lp-`TlAMV&G|P7Y64j3`Gn) zft@1luZAQB9>y+Q-?z}v#1PJOJ`&!zn3^GqfjhBVk6VbLis{Wz#e9b~I5=&AA&NPG zC-GySB103iN4RPDqi>-hiTPN#VK^S6*igiLAncpY#41?){w4xDuoY4NhOu=q+wo3{ z!mWxqC;Up@PZiLjn1AplK1~_YnwT@#f-h1BwIt>*;Xvx!9AT}9IfggzeXhV3#Qcu8 z@Jr6n+Qs~gZ8(xUxMnej@wP}y{Do4hn1gs1r%N?ZqnK~-p0HjiMr~pa;6q_$Ta=o_ z?8he}Zw9BO7BQb=U+E^!r?i+ke1@+I_c4!>V$KPV&eR9iDJdo9tZ?u1L!tbOQbNq% z!sGL6f$U2rx0ut|i`mS%3nr(SlfpSlktO~@$tC6lzQn0adA-RY=1+Vrj4jQOTFf!w z9{FtIoam$!GmAqi*!N6BDltd!lS=kI(~v^UEPhK+H!HEl%;IR|nnj2y=BRMRF$iLb zIj-O_WC~)4IjQh5WC|ud{{!?y!!yr?==#pDis@h!)4?dFgHcQeqnHl< Y1)a8Uj;zT&d;kCd07*qoM6N<$g1E%l1ONa4 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c856f9b9f5e0da58b81862a4551f939471d9da48 GIT binary patch literal 1465 zcmV;q1xEUbP)h($8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11vW`UK~#90?c7VKu2USx@y~W#j$6(_?ve%c@Am@tt0z$K#0h=Kbxa!8IsI7b{D$MG8co!oYNzqjXE&w8H!cd}>u|J(0( zKkHfRzt(yeMx&9W+iX=jK(ah3sRB|`1*D`3NJ$ltk}4o2RX|FrfRt1LDX9XQl(Yg1 z4gUVY(DakEO$#-8PtzaZ0#;({hU@g2HvY>Ou%mdYk|}6wC|bZS;;Bj|r0w^`D)Ce$ z6Hwx%zFIt0$(WRyd9B87g=$wv$?5TG>@C+)%Tjs`vkLo5QA<)<67haINH?-{lCFF( zJL539=v|bqNFt`@M_}91LApklB*6|i3fq?s(j~ekiRelkD~7;CHzg56-U(uwK6tt+ ziI_y3h&@UNDNUCp5gUgmV!zTsO44;nM8*@uT56EgAc^Qm94DsegQFHnTQ~~Gmk!Z3 zwMl}*ajF6J`)ZT~hv0NEvFJUuN`eD%jscvVs96#*H9ud^+eo!bg1vB&9=DPDAc@$D zzf@1#NPUq+Y#v^YJxd3fH+_;s>`7gT14;*(Cw-Fy+u;fvS~$nt=%XZJT7DUhDIH`U z^i>kE(7gnwlnyd0eU?PDjxNBNrGv~u-z9C~Tp_Eq1y^u}EQ#3axE=eH4zd(OmqeWFyGw^p zBc-HZl@N7g=^%@dlKy5#+<}uz2l+24>ECRJTX9C|ApayK{ht-MQM8ddK}uSj1+n10 zs&o)i(sEpl>lALxrljS$RJ^B(9mGsY<8dDD5fh8nNlD{#8t%tVZ5K{SlW`Ir#jb4@ zO-Yk;B%Z{+Z52#O({V7K`Rixnq@?ND56|J~rVFK{+1MS=ic4*ck&P5kow>ayAS5f|d6rV6B_+4xp$X}r~BksR)(XD!asupm;> zw0t6NzJJn2v6M79?~1PYmu(eHNt5xam?V7PX3>;1J}-(}89%jMI3(y&PW_Peyns5Qqp2<7FS|kC>>-ADe2#A#Lal6bdZ0N zlKy6cI9~j2=^+0lB?aqo1J;xdvKYBe;u~@9yOV;*CuuEi!gr;EEX9x|t-)<#)2`DD zRnq&o8^4qeG7dwO^t$LIY$_dOT!tp;WjugCN(Y&QAxU}>k1NwVW=WrkR?p_r zL7JvkNgsRAuMk+M)}Re!v$7(ubRd(vsGT{h$=I1SKV{)A1`OU7?hu zbz+!H#-N0xbz&x$j7i&)){DhzG68K#8j0bp*Jlx$q76wSF}&p>bxN9+G!nyGE>dTp zX-S*p8faz~Mx*=;yH0-DMg^p#3P?#6kdi7OB~?I5s(_SK0V$~hQc?w^qzd>Gf`6!{ TG%zhm00000NkvXXu0mjfOW~$K literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json new file mode 100644 index 0000000..808cdd4 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "grey-button-disabled.png" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c0c12ff8ba0a53d117368709588377e3e0230b86 GIT binary patch literal 974 zcmaJ=O^ee&7!LKaEG~fG3ty?gqbc^?aj&&D1 z)R*iOBPD*w*@93xNJ85g5(c`Y?**Fp^X#n%JXaI%tCnnq1{ruq(})~T8*OJgbjq%H za2r$-%pLfI*&y*xf*2>7IP;77IWyH6fZ0hdh>4<3}S2|C@4=rouIEWX8;w6KL9YQ3@(D&24 zX6Tr;$hnNQ=(s%z32c#=jv|Nmqqn;x^V(e;C$zAdmQ>Zy z&Dx=%B2>+n3Z-fl8Tx+7tf~3^>XKUzVrB;pS#rH|u5lq38xgS?joOr+EW6hnP)6ec z4S`|Ix&^Y9?RY_IypVsK@u(w8qZkW1WGPnxF*SZYmTz5_O3og(8 zKU~Pgz_h*pw0mjdGn5`z!^pF literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json new file mode 100644 index 0000000..03197d3 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "keyboard-grey-pressed.png" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..353fbd4cd0138fffa5d7e65455d1fe603b9af18d GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!1SBUuI~xY1*pj^6T^Rm@;DWu&Cj&(|3p^r= z85p>QL70(Y)*K0-AbW|YuPgg~b}?~1HsQx7p8|!wyk7V!3OF)ZF-+EuKSmfeH&;3gQu&X%Q~loCIDxhEY1J` literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json new file mode 100644 index 0000000..9405a66 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "ipad-keyboard1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "ipad-keyboard2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png new file mode 100644 index 0000000000000000000000000000000000000000..80689a128926c9825afce1ef2f1ca56994b2afe1 GIT binary patch literal 2417 zcmbVNdrVVj6hF5x)(V8m*eKFzH&k3ALqvH*WmS~NoZ^FM9kV>Nbi_xou$584yNQ4s zC<`VSso<6@{y`>nIt8`R4Fo4X$P(8YMcEK*u!0Q(TPbDdyRFe`fo(VGcOT#BcOJjr z@0|T>R>yl!nKlIgyb}|auLE$$M|jNkz_mvE)_%OX=PXN1X5+P+y|onIIoS!DasWJi z$&dQItI&!c`ME1Zx$9+_xr%hT1eR`{ZS#pU&Cf}ALh+QA?;KEO3Xy-@lV&#(b z+$`BnL2}j(2}FiQgoZ769JfdY!0t#~zBD;c^{A`)>!U09bb637sN+OFwc(cN<`KU@ zecViT#BsGre8z8TwcAwd-hJhd8uu58Zi+&R{NhA1LEgHu@IpgDji;GcEE(+Wx*Ivz z-QQa`qCnSl<#2!BYxn?TODJ+i2XV}#FoQgl+9zI>3woVGg%qH*Sho}u9`@!0-+5GUCU|w3? zF20^#O98!p3;O$XosufuMF4%taX2PCrv#uggNbVVa@@xK1&!`N^DsvGYQwq4&6=X3 zmO^QLaFkI|S0yY;7(rP#>lgERTbI#0ppN+$>`1@-_qznQ-!Q~EUR@( z)L*9_me_HZ>U>IK1}>g5#MZV~A59hOA99R?{DCn`Z{Mt$7n+_*hI|Dri;P);CY$jM z;{D%I4;bb+(1}XXU_EB_Zwv*vKcCs5kR9}#Ab`_e9I#nb4)FEU2GsnXN&IAc3!w8} z!*US59?jlt9ttZ}2h06AW#jdDu zG6jXBo52ZzG5;8Xo-?)Wttjo%J2XIbj~Zb?;tD>b;Mm1rQab0B!Bo?!muM@t!RGJ7 z?L`9h!aM2FnF;tmJzsViTFs0KoMf&=poE8FQHEi5F1>mthJqJG+S8FwSRgEWt}SgQ zfB5{3m6z1I?0j-|L{H!S&xd@|c;D{3xtlr3A#~!oRPH@dCnJrb^PZP*0w&J8#}!H+ zT-j1@P+tewUak-waEjW?f^O23UL7okq^oRWJ`;4NN!ot*FI^oQpVawd+A>F4hg%BI z=5tzA<$0HMNqhJv-T|}K6r^18WkF5PpliTKZ5L7d)=`tJt_lL~q$k}feIl(T1r^d{ zouC1|h(z|8e2in|?Rqi56$!J$B7$8ThsXu^f^{(3fjJy!B%$Rzj-PwLMbpM+|SBNG(Ly@Jv|OHejbr^N!4Pdp71s;Z}K|Qz!Dg*zB}>&sWv+ nzr^VT!PhXw|2R7A$XVUC#q~5L6l8Rf0}@xPUVdU(+E@Po#>Yj+ literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc920879035e15a6ce2ec6bff8c36d8beb4a9e6 GIT binary patch literal 5989 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 muQHcYA>_+?Q(!z@tFOAy8?j`U<7QxK!r&JTl literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3a411c23d2138c4806a6d0383a3ccb590869bdec GIT binary patch literal 2383 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sU`*p+1B(2$&{hOeEX7WqAsj$Z!;#Vf4nJa0`JjF5GH#FJ8PEJtIzMO`v36pR|bX&ACF#SW?=a6e^=Fq7oEHhBrE>D`FQ5cp6buv9(e93{Qht6 z%#Xh38uJU|^7b8{yMLd3g-}|WUG3M;XU_cj^5}ZAdZM~n0|SF0lK_K)8v_%=5e^52 z1_?$E1_5OTMusFmcYcNg%VLz+Zf{~_VdxM9DeY@ujCjH$QI^UKQ^lgd;LyVWlgD9{ z4J$i?_<_SUciMPi$_rU|4oIc%hdc21hzd1|}x)19sDHZ-A#WxaV+~ z{85O5A=}25k;p*nUI0wCY8f}C!jm63(O}y0Sk8gr+732>Z6qdeu$Q-kGKXOTHd~I( zcVLKkY_9-K?!XkVgF$Q)QsThj9&koP4bOW_YzY&urzb*^EilU0Y-DK3y@{EBLE6q- z{mn1`x1XCq@4#j8{W7*pv%z7!lZXOidVNjx2NhsZ@%QX|W8=?Xex4>&V8GpJDQnBX zF#A&vLyk8C(*_U5L+fN1nbC_ExN3Y}2bDn#f69TW=Rg=xL$(Ne&S^mdtR>D5aRY|# zKfL6WO$Vg(4<1Cy;dqL($4VItVmmpd2$z66*n#nK;iH0Yrf1eXK?5M<%Tm?o8A3$7|tJfo!+0G z_U!KL?ZA?uclY=AXM6IC_-eX87k#$%TQ3JJM2wC9+tipE8{6B|R|1QRBcO8m8K}(2 zd3B3Xh{$ppGt0qUc;@8`Mur<#z5?sC)ris-6c-$(CPH4L7velF{r5}E+{8DuR0 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json new file mode 100644 index 0000000..228a6a7 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "kb-dark-blue-pressed.png" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..37951ab0fcccfa4e4918fb88f0cb9de412ee9db5 GIT binary patch literal 1064 zcmaJ=y>HV%6nD!~Ra6LxrLde_kU*+G&avwcH5A8AXe82BX#^R$oX?Hb+Gp%5aXWUa z1QPv6SQ!`)5(@*YNbE2nF(DxqI45!G5cMSc?%uuMd%yQ}y0i6gY4PTwAP7tCO~1?c z)$&`I=l`?spC0pVnKk=tmxgSFG9tJE9T3ot(H`j%6dXPKNY(}6%1PAgvwr81gJ~?I zQb#W0l%oY>h9R@bNyj199U% zST7uIASMifB0flRr>Ki#yAD5>VnqaF6}Dd&C#3qF9pKT707Et;tZERLwhRr!G__Tr zsZdu`sKA;8ZAaG~RRz-}a&K7>I$ggt^~G;>amZNeD9UIwl1DX}W_t>>ZCg<_Mbjj% zA>~I2Lxq&&E0qPGGLsRiLaYaa_hV z*3Ma%{HHNfJMSH(MCp>89%h)=BV4J#Ja?BnDit{!XFH2{QP6=;@nK97*7oZnf02VI za9q!KUCZ-Z4airsX6aV52|ahs@>@o2ty$Tag@saOqFEko5TTlbBI zp;y>;k~5TGQu&Rz-znC<6zg~yL5yZSN)M_I*cnnr^C3-v=Zz~4?sgE4l2UlDEYc)q zpJdT<60|ZJgK?IfXa)-lhlWLPAZbA@ko3SbBxGozq!J5TkRWIcLUD=@E@W@grCh=z zNh?AQ){q_QCO2eTfdrvxN|vSBl2tQs2*Yro;(@4eib~ly7aFM&@$M{-=VQSy=YvHO zK1~@Pn?GN^?Up0g_T66b^~dMAB>^l5>g@$M4E`>Dc<^T9{r0by@$$7-v)1oBaQ>=r WdTZ|bjo#Uda(3IzE&p}n>B&Dec27qD literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json new file mode 100644 index 0000000..6b2910c --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "kb-slide-button-pressed.png" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..6978659a91b8fddce91ce7ada1d77c1e206e2b5b GIT binary patch literal 1064 zcmaJ=yKmD#7q77kw_?M1R1&5m&R)CGxn9Z6%tZ6 zM*a*Iq|Qi`l>z<$RuC{CF|l(_;?kk&N%q}+_x--__q|^4^d79P+*#o`Znd-R_1V5z zeaqL_|ILrjPuO;w`a`-)#&n8u%sCMmVbDp?9`0il9X zP?KaRK~;d3t!cI_gT>;Rw>%o#eJ@z}Vz(AQp)|84X*!*XQ&lASo&+t+l4M0v6oF|7 z#ZgL8DWt`EZNbAun8z87NeU{9XhaTai)T#dAtYJ1du})_7KvghlS-6HP?V)4sp6Vx z7qpN6)3{W-7#w9->f?eO<{_)cc)bR*++FRcQe z+i^X|F2~EKaHB(R;nqjCaG}Q?63w(4Td-E=35*A5T z6S6}ES!2zJ1kExd0YbwNOjEH0Q`N&U9FIqGIN~KnQK}l}LL=28)}7Vyd@R`Ie6VoJ zrYUD*^EWi2YUDbeGbq3Q{&Kp?fn`qqj@Q;+{k*Bg(yzB4PK9qvw=P>}cLQ$e2Di~$ U`uKRzd|AzI$M1RX+Rsk@0sizuV*mgE literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json new file mode 100644 index 0000000..9ae1d29 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "keyboard-slide11x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "keyboard-slide12x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4d10650541267241f85e36eec354ed9a761b5b GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^zkpbmgAGWwUoTq)q*#ibJVQ8upoSx*1IXtr@Q5sC zVBi)4Va7{$>;3=*WlCHlN`mv#O3D+9QW+dm@{>{(+%k(&%kzt}ixr%M&0g%;)B{u} z3R34>l$uzQnV+W+l9`*zV4-hlrf+DX^Y^_ZP{D3b7srr_Id3l?Qc>vVJsiBR&hw(^I_fHZvOUow8}m2mdKI;Vst0J;us{{R30 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png new file mode 100644 index 0000000000000000000000000000000000000000..7459821fa6a10869635770b85adcbb99519901b6 GIT binary patch literal 594 zcmeAS@N?(olHy`uVBq!ia0y~yVEh7PM{%$LNx$t&762)hVkgfK4j`!ENa+CbISV`@ ziy0WWg+Q3`(%rg0KtY)j*NBqf{Irtt#G+IN$CUh}R0X%pqSW&IqU>S?=U}rJdp7j| z)ro@CITxiSmSpDVDTHL^rZQOQ8=C1Gn&|v}FA21MwWo_?NX4ADR}XSB1&Xj7TrS9| zmg=FlH9?EJJKT0=TrMr#7t7kR8W^t(p00i_>zopr0JGd_tpET3 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json new file mode 100644 index 0000000..c9fa654 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "keyboard-slide21x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "keyboard-slide22x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png new file mode 100644 index 0000000000000000000000000000000000000000..49a60d22e93d268f216af47915e7d6829326c157 GIT binary patch literal 351 zcmeAS@N?(olHy`uVBq!ia0vp^zkpbmgAGWwUoTq)q*#ibJVQ8upoSx*1IXtr@Q5sC zVBi)4Va7{$>;3=*WlCHlN`mv#O3D+9QW+dm@{>{(+%k(&%kzt}ixr%M&0g%;)B{u} z3R34>l$uzQnV+W+l9`*zV4-hlu5V~E#j&FXs9=Yui(^Q|oVS+`a<(`yupGSF!7|xM zWwMZu>xAF)0z3NGbtOEzc)9fdzteR+vx=kLKYo|E`~Kl4eUDZ0w(Ueouz<&VDNPHb6Mw<&;$UF-E5`+ literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png new file mode 100644 index 0000000000000000000000000000000000000000..50760817b8c3ea05b9508f865b3276c329aa5de5 GIT binary patch literal 590 zcmeAS@N?(olHy`uVBq!ia0y~yVEh7PM{%$LNx$t&762)hVkgfK4j`!ENa+CbISV`@ ziy0WWg+Q3`(%rg0KtY)j*NBqf{Irtt#G+IN$CUh}R0X%pqSW&IqU>S?=U}rJdp7j| z)ro@CITxiSmSpDVDTHL^rZQOQ8=C7InoM!*r~z8P+|$J|q+-t7s|Pum0!3I3Zg*3e zF-39almxA&H=+J%8#Y|nuhrCO`Q_WcH}_}GeErz+%ied}XMX(dRkKfS{`#l?zdwB< l%W!~^MV5hS6w@vY>S~S&^wnt|(+0*VgQu&X%Q~loCIC0%VKV># literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json new file mode 100644 index 0000000..5e86f7a --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "keyboard-slide31x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "keyboard-slide32x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png new file mode 100644 index 0000000000000000000000000000000000000000..82b639c524a87a7fcc743ba002b83f66a0502a8b GIT binary patch literal 466 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Yobvf982BR0prA~NYeY$Kep*R+Vo@rCV@iHfs)Ac)QEGX9QFgI{bFkTqJ)3%f z>O?{6oQqNuOEUBG6hbm{QyDDu4bAlpO{O?@)Bvs5_H=O!shIQjwyob`2a(o`?A>0T zliQk}Hn}`*5;FU$apJr|fJ37CMcd^CIoU?@d_HOhP1!yD>EhQ5ewRdr*6t{`v%mLl z*0HadCAIaE_s!$JntXY^qyEO#Pv7DWH$A)hXU^{DkyTOeDr|dZKc8p!yYg1zoju~x zl~s4He}28`XYu;EwLgJq_it}~h6h0`0v)Oi4myl1No&Opysg(__#uCvp)rDq56zoKO~w9V#FI3R^h0Uo`iNYyeO(Q}VhJ}R>e7_JPSu6{1- HoD!M<$$6v- literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png new file mode 100644 index 0000000000000000000000000000000000000000..77d9cb5651d504ba8bda6ed15cb96b99870f5ec0 GIT binary patch literal 782 zcmeAS@N?(olHy`uVBq!ia0y~yU}^xeqd3@rq{jE>&OnN#*vT`50|;t3QaXTq&H|6f zVg?3oArNM~bhqvgP*A4CHKHUqKdq!Zu_%?nF(p4KRlzN@D78GlD7#p}IoRyQo=rVK zb)q13&PAz-C7Jno3L%-fsSFnShUWT)CQ}?cYJk?Kd%8G=RLpsM$1v!&1B1gw)%Hb8 z7J5#6e2AyFdrHAd-&8hb&O-?s&epHrsQdQKI#u!dkRPw=zMC9BBvbtK#lJhJw->+t zvHD^0>pTB`JUsoYhW&Kj`mORiWH#ht1{ zX_nWYUANsME-fFYyV36E=hBMj`}CLJnDM@MYuzmS{rSfwlJD5Q@!KP;*La^>{^9pS z;Xg}X@iH)c*vs-DGQ&obnc-YB0}DfkU;_h#A(H@uf*S)9!x0V#h6V{n4h8{b21bS? zJw1lM$#M(_p2x%hB|li=P{yLb;Lt1CDmUG|AxU>gTe~DWM4f DX2sUs literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json new file mode 100644 index 0000000..14852e1 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "multiplication1x.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "multiplication2x-ipad.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png new file mode 100644 index 0000000000000000000000000000000000000000..8c8876d61ce82bbdc6b14e295c19a7939e9fbcc9 GIT binary patch literal 424 zcmeAS@N?(olHy`uVBq!ia0vp^{6NgZ!3HGvIz4{^q*#ibJVQ8upoSx*1IXtr@Q5sC zVBi)4Va7{$>;3=*MN3>GN}P*Q6H7Al^Atidb5j}2^$iX54Nd;vJD>wpAOlhmoS#-w zo>-L1;Fyx1l&avCS(I9yUzA;};2doBV$Y@?1_nk(PZ!4!jfuS%H)yWVb^nX*Otj9IGD59UA2L2KB2yu5mn4mn+#Tim{De*eMmuij0!zW4UG z-TTir?|;`Y^U5Sy{R^CNgi)%|Jf-5$CHW()Hp_mxB?$7@t?JYjDO#eK^T773ZSqc5 zzXvv&X%>fb4s}^%9@aCM8lTBBTeF!*|Awz#WYOJY8S8g@?YJ8GUE_KB@4AQKN{>vH zwtP`qBqn$@u8%pwS$A#ZQA4elvB#_I&K!{V7JMsyhnr?%@eZ%QJnj9n?$znymWQsqMVS_L!t)*$Q)&T<0wM$2)0zuk*ZPM{0qA O#^CAd=d#Wzp$P!vqNiv8 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..2faa5bffaa48ad069872d2fd0431869368dace0c GIT binary patch literal 749 zcmVdbVG7wVRUJ4ZXi@?ZDjyAFEKDL zF*N_)z$^d&0338hSaefwW^{L9a%BKPWN%_+AVz6&Wp{6KYjYq&Q#REv-4WYewk6F}E!FUOi5m>`q{3^JHw*U0<944~2YnU#;#f3zmidWdZ z3TQVT;BWGt9DFr~dM*zjlg!dM+==3>b27Q%h_yo~TubTPQX0 za6DJ+z`dB?T(JoKcN|4aqF2U#3`GN%(wz~yr?;NfEjT5#R$35g1S+@{4J`|8Qm;;G z_=8JmZ6;4Wcp1H1%7dEFk6#+dRO9{LW@;maufqk4+aWE!;^an6#e8pF5j2DE!`mc( z^-kgCR~*5HRY5ZYw&7s|XmLJa`+ox?v>i7vy^P0VR32&2cFjWj@gWQIayhOF%ix5h z?V1H{#$};M^1><#XhvwQdiND!E_5^t-G%2C4ujOIYRqLnb_nyl-r%SS6S1!mu#D6A zorN=fs1oztndoi8MT^@m)twS{LTe7~5~?=0yil$QKq=ms720x7 zp>z<#nL=vq44$B?i8Qt0dXoD*YQ>^BS;7nRiy;>p#~EyFszBxq7{IqW&dg%Cgm)2~ z3Kc&AJu3lb@J`&Za7|WY1V>5{Fq*(8F^KYNp_u{4g#Wq%I)FVdQ&MBb@04TngzyJUM literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png new file mode 100644 index 0000000000000000000000000000000000000000..e03c22f4f9acd3316032f9061e720238354fb4d6 GIT binary patch literal 640 zcmV-`0)PF9P)dbVG7wVRUJ4ZXi@?ZDjyAFEKDL zF*N_)z$^d&0338hSaefwW^{L9a%BKPWN%_+AVz6&Wp{6KYjYq&Q#R{3{WWe@^_mV%%l*a;Te*jNaHR$2&Fg0Znw zL@-q7Yn*aV;-NAVU7tYI-=HA0GA#{&%gBjk1zFKgBU9%3EMBsh;lJwW#3 zWunoH+S37BaRE!EVDVhVHSBBy*oKQ(t|;~wu3|?yVHD3QU>TlcUj<|cQ?cqi$$gE3 zmDOcg)`!a#=aypyXG@Urk_+X%hCAqQ>ve=3i4`>xkqp!ETZdM@VAJZrqK6 zWm}4s+WQ!Ly9QJM$FZn-HKV0xI_Y%N;=2&ShY*5_SYHTXDTFZFTluAhfjG85I#`$E zH?CuQuaIFp-C%6z@n(ZTnZbc>Aro46UY=B4$9)W93vT**N0#?8PPYRLMxlB|@*E~A zat`BF(p@oyPcV`ujNyGLq3C2iS~%RHzo!o(f8lNwT+xS#dC3Z|A;iyU?$iRSWUy@U5pRq5*?a0Om zKY^_a5+g#Wh>ZaTMn;BCNc@2goby$N(39-Dd-Xi;`@A3b>ag`Nzj0?nlB9gIVYS7+ zlYQ$s@qc~s?XlQy^LmeWSf5Yam`GKh9TCtB+yQA5*FSmwmF!E>+BWU>c+Yu+Jr*c# zhEdWW5^PD@KS(3j8xanU$bg21{O9Aj45)9&Pc%n$B9jbh<1{9n(^l6z9eKJhA3Om2 zDHaX_!d;LC$6vEQK^7*TrOi(1@k3~XtCeNZOfj=5;sF0avos_CzFXXDJd)-K&0zBREw}!EC@s) zISIL&7Q$p};lUz_7t@GS7J|&9d&I`vkcHA&3PI#JOUGd{FO(=5Ox*|~MTJ3-lZe1JN!U2{L_hjl3$m!)^?@>=P=hVzjRO+$re(;&qWILuwyBvVR4&`K znvGDsR4G^Lb!1j|E0(R5cIyjnGfcP}dSt<+SKQLITx`a~Ux2$)yXD|N($l)wj literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json new file mode 100644 index 0000000..611ff31 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "keyboard-num-pressed.png" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..1bc84a482c7c7239c16015317a076704e035ffdf GIT binary patch literal 971 zcmaJ=J#W)M7&hrZRhktErjt9gpvLEOk|wrds>B~85{{xIf{Zx!C9zWbjC~UrMxc|Yz|tNAdycXyBDxNM`Yx7mK2 ze!Cg=fAjOpW47I)Mvr#LfKKfQb5)o0F=+Vq5Vx`I&YoT33dijndEFlES&tNl_=25c zgv1XSo8u~{NoYG`OhF$Hy+GxEpI`96b5;IH(Gsmt!y~Ugk8o$+>^k$YBfI?R15im6 z=D^3)28n+b#7d&_3%?3Gr)J251%!@OekrPFwSYz<42nWA=ZF#lC0RhlVyPq@14%>$ zC?bgRIV3BEf+C7wwRjdSatBIVH&?OPP31?Fh6;q!=~S5J1riM*l4Ti+5|pGIL*(LF zK$wUli6ya6GR;~| zN65&R3uVJVTJ@x?o5lQzvF0{{nA(AZ*Ie(4Yh24!vyUm+pI5_Z>K|)QU;YIM94Zn3 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json new file mode 100644 index 0000000..9f29cb3 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "keyboard-orange-pressed.png" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..7021eec159f44e88c16cdc644326bfd787705f19 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!1SBUuI~xY1*pj^6T^Rm@;DWu&Cj&(|3p^r= z85p>QL70(Y)*K0-AbW|YuPgg~b}?~1BcpKJ0HBb$r;B4q#NoH+3>g^=IG8sW{kPlD p#-sD*v8TwAWySAQg|LGCVoMh@KDS}aKLRv@!PC{xWt~$(695S+DZ&5% literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf new file mode 100644 index 0000000000000000000000000000000000000000..40e6cd463ca22cad8d2bbbe140e984ad8ebe97e6 GIT binary patch literal 9594 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 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json new file mode 100644 index 0000000..f09a029 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Backspace Small.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cfcd8078a2b2991d4c9ec2b20b10c4707d7f6328 GIT binary patch literal 9607 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 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json new file mode 100644 index 0000000..9c07ead --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Fraction.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a5a3f969e65392efd0be5eeec6abae9f48f2a8f6 GIT binary patch literal 9413 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 S{IVVwBq~70#wMpKPxfD+9+Xr7 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json new file mode 100644 index 0000000..3fb6c37 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Functions Symbol.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fd14e50103e26a5c1b7b9da7c53b78e677825dfb GIT binary patch literal 10234 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 hls&n`LiyK-k#A%WN&ZB literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json new file mode 100644 index 0000000..2335dad --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Keyboard Down.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf new file mode 100644 index 0000000000000000000000000000000000000000..de758f58c2a5673d9d7ecb678bb59a3728d95f1d GIT binary patch literal 9772 zcmeHNXH-+o)}~3X3JM4sL7J3QAVGRZX#y%8(tuDy2pv?AB27970)liYqM)cW=^zLQ zBBB(LUZjgu{SvRQdhdJJ{qDQg_x*d%O0wqc+0QdG`^;p`b7l(}s%uJurJyWAA18YU zSQxF7iU)w zIUfZ9B&jD@IZ~Ny1_}VayAYie1XRcs08>3ffSMZt3y_wQkwk;Q(f~ME3M?%PflEsO zARtn2!4T3%5(JWiLFGu*KP~}9Mp8+hfU%P^QrG;^9O+I$z=24_%K?Gj-riE)P$@Tp zJrImQAb=nU5CV}Tc}RNrx)PB-lCBxd45Tcpz8`1pId(4EiSq?@4g}-Zcywh;_!gU|oqGBpmqP z;z%LY)B6eix6Qe@{97dcw|M*$j#NQv4hsAk0Mhv6N$tq#xnXd2zUoLKR*@8ZX-SZ* zBm`mthRK29a?)VZtsDp>5BwL;Ujh2gp+=fQMC^aV@tfzraQrbN{~MNn^8JkmgO;;% zBe)=miby=(8HYxapDd6xba~*fs^9#-$5~F@4ed#O9y;oZo}M_290UrN24U=wk{A#M zCJDBKK_n3fJCvjy27|!B(J+Jzl0^6??4NjbTs??LS2XqyUJMol2O*&#Nm&_LxTGwG z#0!TZppqaNG!~6Pqrq^n>>s?pVSnQ#jYQ6n;D+%;V+o3SNHoB}7+?wjgQUnGzy(hv zf#?Sy{hO${xe~EHM0xVie*u0F$pgt#{kN(Ak9GCkN?KT?H4Xf^sJ~bJTnYbQg+f*=r3m~b=f7vy-{Ja85%^2af6uP}vvB<;3rGq>LBN}|=_hNA zAFAPp>L4qUAF4ym&C`_#00YnCFdi1tWTj$7QZX1DQq|4J0we_jkiRkr(hm$K1+w~w z8u_kkeu)?|>?a**CqvpQ17yflzcG<+We_9|E0QQ8kUV~**@$-7r9+6{vR{!mR21RVBD8~ISynV&D8g0qh7ahl7*VP=C8|NZCJhUcC~%=lmCtVl|9+M*rMudFKjm^d-?(+-Fw7_!aizM z1S05daCC&j9TNZnIxlOT;3Z?mE}*3!Up++ zo>p~;;@E<=&E<`UgLV4ybw>0TPWt!-<`@iger7`%FrNhVUu^H92t^ECSPlQ2Q_Q!xUz1O znqZSikkip=V`Yk*;X9!Mj|B|*nrWjjVebfn-p?(k9*dY8F6Z>%G-&5a&ZprrOHY-1 z6emOFASSuINsg&5!;r8hCe~vgzCLcLl{qo1`jPigiA(yBm~J1QIJ9)&eT6KRf>B;Tc^F2yz538|+)yA-9${ZQrBGL_cpw9;v=f;ZZdC88e& zB$@q3VjHk~>tERNSUx^}Rj$*SUpgg6L!dn+cn0A#UvX!GZLCtBBYcR8vEY=xxt)&- zTIsNR->~(1`b9o^WE`VPMpZt_!~hKJq4wDl9&MJtaozMVK`rCn6pd;jV=+7I_B$hR z++HYrNlC<=z00>4*FOQEG*Rb1{8V5>LC~5Or&X+0$W$|yD=Cndop$zoZqOEhMV-q- zSg%L*WzvO~i0n_n&Fb7fbbGB#r;=a!H@{f+F}8WHc$YcRHZHNz+#0F&`cC5+#%(|C z_@c~_INHiLot`&3#HK
X{yZYPEI?Vzu2F|F zKiljq(*`U%yORBQlkJ-uZ{zZ*^(kCDIrQHispHiTz1hKaoMG_8iF3D8rIGDo?846h z!05-dO|N;Ga=8Y_x|-thGBX$|j|vT)ii37Upe6aYKI0ceLgtzoGFxvb#70sZz{tG} z_@I2gwI~Z^ok6!M(KCQ=>YmV5n+k*Lb*7D%{)p{^>L!NSr-!RyNSkMFhmP}>8ge5o zyfTh)PI{Vw_$uEe$Pk6~Iz0^fDm1gupI?udWfY#$5MdRdD~{pQd^yETjc7$3*Yz_V z@!_WFO`B7k`FxS_xFz4b`yTq7a;A&Ee`C%lh@HLrJop`}FKvO*Dz8)>bJTq%8a8Bb zL7T#qC=H9+0#|oe5N_Tj_OO-nw*rH$Hp3hjj#MvUI%tP)7koP!8BB>z+(>!#MrYGD zR>)6T=kC10SoxPvVU}oxWsd}Q7(yAph!Nc%e!#9DhCqO90`aVSeqS4<>~x-N@(_r$ zEFxm}*|ou^QS2g42pZnE?8k2NmlV$phiBOVoVCv?a8Zk8+djS#rJ^lMRm_=fPkw&X zuf%xThh|1kW+uCs;Rj2akyOD5a9$Z(t(O0kN+fHPeNdL&ss0p(bY6CcBf()o*(iNh zmqqH8MPK?o9`iC*m9Q~@7;P{DwTN$;RGZZCm%6d4VDilWCUJTrDHwyTxDwY05J z;jS<1pHTqxLyYaozf_&9WpPQ)&g|lv%otcXibprm#D|xbq1>iVDV#Hq$#XZAy}_`G z8;!~-{LJKRiVmtO;0ZvqY>vEqB}R3sX7l#-oLE(TMi-q_T}UC`Ze$~1{&4rrftzxk zZH1`T-Sk1QNZ6ygrYcE;^g)x&7~`{83a*ZxxnK^J)(k!`SMRsL;H65Bngp%5oa!rh z*=Qc3?lj<%AK)@GfqUj1N@yTGEGuvh7Z-D6JC1%wgZ@mIr#d+KI{m`D)r0C_-Pj>P ziHel1$r_fzFR>n#iK_3dkj+O$a8FNWzsGrAN3;6X_boDv`LYuf`6pN?pWcM?EHSTu z*0O+>5yL!V94AG*c$kHoK)~1&J@-MC?duMD8a8Yd?!1=`8hH!oe7WdpSUd=(7U6*k z&nqS*Jc~DPhR77JYS%V3z31?o4^QP3mugdG6L z-NQ{eUInF4Egvp5E?WWd+bzvhYmj6Q46v76tJQz6$4d>p9HadGFRg*XQv zZP-wESN*Etv0CkNr!3H3B+~wd3)gtT-NGB!>%Kj3-FW=rWhC-Jzb8is7t;U_y^^i^ zLSL5(in@l4S!aQ6)AMB1+2sl!kB7EP+((kLlTz>&DpvPz8Qr_5VR9kOO z{gXqD7-VaK>Vma_iS~Zd(eL{$I`_xQFE1S0 zMYTPemuoSd&=Y5#=%}ycb;s?CvRAbaiBe%L1&P?w9F?(p`s0Ehli>uxfQ{>f6_%>b z^5h2^$&iKWm?C^y`&^f5*_u5kcWlyNmP5aeoTdz16u``#piiM!IxTvH?Nq*A#{pZd zW?H*g!v;L2P`c4VH`>v3@S#!lI^ZZck)g2YIuc=oX-E z>#)%HGrY@4%Uo+0q}{XRIE3wP$k&wQ`U(zapah#@A+vJ_tUhNw?LAkf(v`&fr5WP4 z_Y&_DL~ED74w-un?L(e2m96sg;-nQ0XkWG=Ps*1bl)=6#>%O2`*XpM@9J*K47+y86 z{9q%JHN6-NOsDm_sqmFbZ$LR*6e7X0dEDH9T>Qd>a)BQ&ej*pAJZt@e z2NxZaFlleeu4%F@0S-&)lxZ8CoLbgGt!yz1&l<|g^# zlQsn~yy$U*f+i#GmNc$+mW78>H~k1}>Jj-#SmIO(#4{X5p|Q_gRI6Kg48xpz?S}lz zNl#w(A=Yihi2)esDQxuwKi;D`k0UUh)akV!k%A{@o|nOhd;)vim-5vDKpXH=xNetCMpV)#W-hdQOM zbM5^2Ez>9bFF4O$sT6#WzxDQ!|F;>H)qth&c+&vhIYSYlv_@^6VeOcQELppaE0+p3 zJw?5(hvJwI)UEo1McO*^J!GXZ5|?u~aMx%Bq1Vz(e4o{_9oJZ!wtm?wsLN-y7w~x% z$4f8B0+)mF-wGVffG3;vOWo`i)JUIMcl{{9@_lxB#Cbai4BMwyy2Z%7m-}eo6^vH-a(gi~~zLSW)u;1K?zZpr{BWy-`IQV~G|( z(wkGOUpj$YO@>+kb=1^UkseqK06|7rky{~GknetQtB`esD@jEF)v;bUG}cH<75E>- zg$(J0LmvSBU0`IUc$*jx9}VxVldbDJEC3MNv+?2C>Y-u#mB68A z6o&i3G)+WP%4@J3Q_khZM$5xTx>~+aakEphno>TWu(PAl+E%A}ynJfkg14EAHFx)K_ zm~6T4yj3_OUekFiRd_0g*Q|%97M2xVV>p&BG?5!klVf>H=!1Q303Ei@L?@kM`itnm zz;=c%1&A}vrh8U6$2kI`!O626u}(!npVWe=C9p>AARMwNd-=etQ?!gqM1=RHC!Y#+{c+fpteIwoaSh=C7dl1sc_0P4^(>w~%vR|eg&=kF)ZJvpk#THSPP z;`l0sM5s+uBE514J@qS1Q~Kj;>Z)<3rzts3ImHX)Q59>X$Fb%ee;T75?>yqxV85uivXwXh@K5FnmfvHHTk{5#v$L$!&mP@v= zwiXqs52)WFm(KFBwm;|j$cMl3gxeD0DqT)f%{CNWb8WGiWkPt(r}oSk=SnbJqsJ;L z;8Z>1Rlchy#$!*mHHgG9nA11~vz?DR3C(_(2=K_)tm0#?Eb!gZ$-JLNE11a32BYlHIBcK|j*Yzaz+ zNut7>2Iu%RREPBu=bkG2D*LMC8?h9*zBavg>sh9`L7jTwxj>*+A~ba)b+WT8u_WFp zc`?;r$RKGb)jQQj>W+xbsjKI{7VJNvy3nZHXdIMVaVbZ0qN+(DR7WHIZZhfyDjgMp zy8cnR^@31sLQq2WVS{4j`HFs_{8PND1NpxFPpb5)V&ed%) zV1=+2)1uRS)4bD_FN|oLK(^aZBcA-|#ORvn*bUgs)85J6_cHf!*57Kyl6TIyo(V`6 zNVZF+Opd*$d(mx>b1?Is%RPhFAV*v3r(`RArRSxD%k7&%N&1WB3gObyhyG`tb zDx(lNW#yj=C0{A#jg}~sp^GX_?Q``jdyZHoNLk3LR9jXXd#f6p2buzH?q68RQM_=c z>`q>TR)fXW7ti|u?`)$~ZZ{U?MCLRgvyizHXu*5&C9lrpn&+NvgQFk2N3JPOS_oG} zWa=d7#B}NzzkNMsV*9MOs+ZYm#z7cXB3;vdw_?Q1tW@(&HlkU+BOurJ_KrA{LX>G# z=F!okHWHsCj3pN(PMV3C9Wr&PTBz7(%1YGWu3w z_~6N&INN!)c&(`Rq{75m-d-l_8>#m%$6vl*o$v*<;YARt?Ii(qG9t|i^IC}Cpm;Y%mLh+aW(evSxOCK30m!+VVDV0m_2#=+vu)zDF>6-a*!_5#)R2cE zuhdgptRm1l7eZAM$u>D5qWhd6M&^P6l@1N>>TQxqL_*}mj>jH4m9$xLz z?25r2A9sx$dwn7MLPfo;L*>d$o9a{53lnvcJr=CatQ*|% z6P$dtS3ABKI+dxov?50)N}GEZ?=C!Ru)k_^RsECpT70ios8)V^Q>^FU+1Ep|MY2gU zO)&{=4<_B)-3~r{R`WknGH(dYeY#BpKZRKP*uH&oC5u^p*f%op-n_`covp`x=8@(( zZ${okJkFEB`Rn?R&tDtJYd=*Pd!2%C?D$zO1*Er_LNZkzHV>-Vrq$fIl}5}U}j+5r?7*VHz#VcQf!v4 z;N~B#xYW(#D-Tj8%Wl>m3Y^`w+5?VlJqw(^`7QG}brkLHvQ)kQH|r6F`M|M*S9HZ} z)=J%e@B@bf^wQ*A5k`YES@)F64hFY}?279)m$HX!dn<3)$A&#o1Aa^1xo^;XsJFOR zT(e4ZL90xwH+m;})bGJ&>uOhY>cC`ZgV@){Uy@&y+_~btS7(pvY*}@BwV0-KZmg&t zvHNDrWfn6xHQDb_3Y%u7b{A6GVQjXL=`;odv%DMVi8=Mr*KWsz-66cI1kW;dZaei3R(11p6 zZlrS#vLFEJxZ1e^$iXKS)h*=J5$bAcD(W&|QWg>nuA-re07KQFvT6_wm^u`!fl!p! z1Zjfcva;%65F84FO3T8fWxya9SWN~30m-T(z=~uULP{PX5Wj~K0fR`h2nlHzXtMkd DcgHIr literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json new file mode 100644 index 0000000..186ba84 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Keyboard-azure-pressed.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf new file mode 100644 index 0000000000000000000000000000000000000000..67007f067b144d6ec53181ac07b0756263d3580c GIT binary patch literal 973 zcmb7@&ubGw6vyqM2!j=&>HS>-4chGNZf288DB7e6Ma8h`O-07dB-s*o!t6v+{{(+P z1dsg-JoKU>_y>p=L5lbn^y)Umi_Uad2c@7_hyToW^2bTGgAEc>+BD4 zfr5N;OzL&8?uuxjhfqPuCjelz!jTpVpChhCQv|Xn$le}_wG#X(nVw$1AV=F5PKVk* z-+!pA`{eVjhi~6qJDXiDc;8*-(ob~Fx$$b}^_6dh7w22QH#Z9BFW<1Wk~?eBjUT69 zw!W@kdOX{_N<`F~XT>+eV<5~EF{;c=n-6CJo8TQxb-Sw(4eE6x;IKHw53m}y!3Hbf zct>EIz&l>^JZzB%tfP~$0IS0XBFWj|N<>He9@*!w7Nr;T82Ciy~XJH`x_P$w? zda`^G%ZUm^45l)E&Y|JY+PZWYC!lx2UW{%`5q!wlC&qLxftO3rz;5V)u2>L_US0un*?}%{v&SLqiPNyb;Yg_Duj_`-1tOb1`%-E!#?#|-81*HXMkJk$HCH-uSalszEFRo%k-zq+{zd=* literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json new file mode 100644 index 0000000..02c527f --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Keyboard-green-pressed.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c7ee3b28009b0a8b36649ea5eb03c30a99e2b6a1 GIT binary patch literal 976 zcmb7D%Z}496lKH4Y>@bYyQ$O>B9Aud168A8+G!aLh$x*M57kZFG@}qlj*B|%V9jC{ zvl$7AED@k?fDJ#uAMgdUg&$zUb<(s^cUVzuANyXPbB|qf2E%)%W|P|Y->bi*2?p@# z8R_*vf5hTI&cQ;+B>lLeMBW2Fpv9;?C+CQ3PxX&<@>wWa18U;*KgKM zm;5+*cKPY{>f$HMg2~K}r)M80SumSM^Jq26oXb`AVV+&Qc=zSj!>c#)ZJ7PBRtMUR z(1oZv`FhTby{*f~- z;2*zr9($7q^waZ%f$q?NWUjE?#0OkEdNL&c03VA62><{9 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json new file mode 100644 index 0000000..02f937b --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Keyboard-grey-pressed.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf new file mode 100644 index 0000000000000000000000000000000000000000..33285581644da8e55e2d19c1447908a2f7e0d8a1 GIT binary patch literal 966 zcmb7DJ&)5s5T&768VY{EG?8*b_#?5M1VyKC$tBzYI?FkU0+84^8^@>MwbmPyqv8ip zP+Um?5)Bk-fQF6=AyHg)zu-j2AApA0b(~n~SXuV&+nt&B=B+D!uYb!nTvGY+d-<2t z!2&)yA?-Gp_gNCjDL4oP1OUu_Jd;e|cSa@av5=1$+1(|X6pTJ2i#Inf$#M0v=3Dl~ zr_avSfPC#dKKpom`T9G18;wTE(~}P`e^d`2o*y~>U9*32?fuWrpKs3jD`(j;uKap; zV?kIlUZumCBBH9RFV^>(YFjO8YnGbV{z49h5;;M;tpFS*wQvDu_YT-#h7{k(6gTjV z&nl0*&I9K0Y0AL#X~c9+p9+>p3@gMxVB?tX@dZ3W$O5Nsv|5g9*`?VN9oglH#Lx^> zFZ0F?1fQoRa(OL{7JP@uK+q&hRo(EkfDb^5xn`t9b@5b>J&!{c4EEHKT$35FjL${L zGEkifN(vSK!q1JuOcT8qk26%Gnh-$2eXc~85_qKqUG!OU0o9Pkf`e_?HN*CxI+ZfL zZDN-ZjRZ}nahMrgL_0gWjxYFl9^%FMkGPFTjS_)eL6=H8<58X){mND@ZDJOJO^5|e zVyy>wu3L9u0tMzYTA+6!Sz~q+`=v$+vute87G`^R1-7u7i+*omu7lTi3-cPKb5hWF s#)SUehVcuAl$Fl`=Mt3g{Q4Xu6AsD`?{$)r1b8`@o!>* z0sP@UX*58;!{UJ)f{l<*06=d?Bgq7=BPv;w1w3SAbCV=eF#3p0UtF7!z4ENmHO#MX z-rDDV^8V)iqgNM?pIx}@ezNR}^ZMN9llArX$xG5YCBL4ok<$+!51$-exqbEd+V|aW zWOnw<&d*C<2#dpcP~0gxii28_Mb%c<>QGs5P1&`kveT2u3>pmu;Lxdo2hhD+V1gb{ zoRKeH;Edlqj9f{uq#kZ4>CHa5~cUBSaifY0YY{FXhc76@btJSypk53<^Efm2&N=stYoq?)u5Gxa_WjT5FKL1W z;_!%cyI?+INvx*eAmkGOFb905n8a~L73;H5M2u{2lT=AYUy|kfJLlw}e%=^a_OGws zoa;XMvGeTg^R3nCPnN|z;57IcWbx$akO!ZFY;?BDK1{RI7jM7Zc<}DE^M+@?oz;$U zEr@R4BrJ*6d2yy0X&QP&BCVF*){Dl5ZccD-sfJ^Pi36z-AZbQBc$p*7KXsvD)%(AgSmoVGwT)`S1*8b8tyz&a>wJYb8 uq?ace+KAlQR8gsNg{`JTYPG#51M&}vvIMdK literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json new file mode 100644 index 0000000..9ad12a1 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Letter Symbol.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..37e03d350127a9fd28ba43878f8bbbd701da2647 GIT binary patch literal 10990 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 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json new file mode 100644 index 0000000..53d8353 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Letters Keyboard.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0bbcaeab6f25eb4c6ea3f5f2827e2da47481c48c GIT binary patch literal 13764 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 D``RRL literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json new file mode 100644 index 0000000..64b7b1b --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Log Inverted.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3a5c7852267e789df54e842d41fa43f9f22dc2cd GIT binary patch literal 10512 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 W@Wo^88Nnd144hF&NCTgzUn3E+k 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 aM!Xh-Cj{Vej`R>P6bYpl6jaq$qyK-v-L@J4 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json new file mode 100644 index 0000000..3035c06 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Number Symbol.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e16e7280a991e2478482c9e708f0341e5a5fcaa9 GIT binary patch literal 10518 zcmeHNcT|(hwx@Rxkq%0vN=*WxgetxF4pKr05Fh~|6j1~OMVfRG6p$iSnxKI6UIl5= zq)C$wiiiU80!NR&dwloZv(|h6ovd$V%{Q}W&)$3X%>4HLhD%RLMG!0`M#=SVzJG|4 z3@i!&0o?4IDW#=>Do9rh!UL${3d10j5O6np1h&e<69a$(wGe1Wj1vGXEDY2E2m_6- zxg&tOcFu+vl&q{L#sdLEQIa`L3!8xSK*k^ulhU#QpFZF#etppJ=RPgodt!td1Z}Ga zIXU*+l8dHFEbo~v1(p)t1%XFI?v9+v7f>5Hqo$=R_@W%&rJrB@6+f>@DGpM3Fdt`- zaQGa#(G>!t3|k5l>I92NHWmqwES}mDEwx0Im*bOwaOhI`!YL7G`|nahA)Q38`u65MB+Zq@$n=qLIIxQ+6xk>|LZ|?$-TuQ@jK! z(lR|zC>bH`A`fxyeWf+xs;ZUHY?Fl6iE&&4Mw}E~`u5+*D*kt51w%yst+1wl2&*BW zuGp!Nu5S7+~#&nwM7-L78E(a+jiRNejBy}{&F z2zt6C2}e$P-Unu@!CF_<1$BhF1uk^~4N3`8*s{0cNsMis^VqJW7#Wzy6tEc>r7+_; z1d^04&L#75F|`fk`S=?UF;PyOzWB498CXy|2!IdMlbZLL3M@CW0co+&No>kfmv zATR(sgd-9y!@gD1zz#s#%dne@YJ;@h6%kHIH9rr8p`VTs+|LCLwP%-;C6o4*^hLR& zuwnvyQLboDNnaUu7*-O5B({7~4P*zLl3-k9*yT?e0A|{H07W+s1VB_sOb`yjE-neM z5Li@PSVB|)APmAv3l_#cf*_D2L_`wX{Qbu+ONK2;d)PZj8YroJ9}fE_!|sH^xJv?o zK0ZD|J|aSH9*#gT6bc1`gn`1sf>;Vc&ueH5%vTWY$?*r1AAFP$o^TJOI|k{72AuGP z*|~XPWZ2nH2Kw{&T`!dTp97&ig-&%8g1e!BzA$$nSO^6CT_=0^pMBiDJX}vjvxfr_ zt_TzYjq$|x1OK)k)}-3ne|G)ta46Jod%FK(kN?&W+khQT1o)=`u=+`3$C1=_vqw5y zQ^Ia%vRLDb3WCH1g++|O;*z2uNf8O`t0V{{4g43%p9VVRp@`kwu-oB(z~dLnf8p`N zBmWaF|Bdz+KK5`)2R9EC3?mD3cXvg?VJ9;S#Hub0{Mqyi{i&TLmE7Q7C-b4MBBy{os^u1sn7(W2^@KSU`V-UU==@Zp|cKFUn8hGOBzqtM%>*}-? zyRfip8u;g;K5hDQCH#LCerovtH3F9JKZE=&MQg+blA)_Ns%w z@nN96Et?)IJ6iAH*mi=>Uk|T3=B<^G-Q|#x5A>l_B`hiePQWl~8dp!dRlk|zR0F-# za0fb;Hb=2CGM5H;!DeE7wcMjs(B|$2Pw1B=cP>@vN&^Ysd|D&@=3UUdfNdpOMC_jQ zgNq^-XWdAIVT$W7Vu*_~lkbQ(;Xq#h)x6N3YRP-Tu}BpF6%z+4!ravmNJl3OKtdD> zJi&7(ErP;ek(2C%BQ{PH2C86zpn`&%uce@fs2D&{1RL^$g+ZbKFg7T(I&H<;2_cMy zkk}y%PAha^CW#o)akLJ?3n6IaghT_?u>mI%E{}F}MF2q9+yKlT ziFO1CLxI{bUsI(0Y5oI*4LC6flnIvLG)g^@;s@Sz^8lKC7h4Ph`8jo`5`2$b|IOTC zUFmy50t^rVfrWpa$yfI+U1^OOI~t(nQ!7GAn}ey=4k#Z7;E?y$lqS%nrf!U-!uAP3 ziW!(Fgwl=(GzRFmMCk7|wekSw𝔣M}?pslAO6@M=|$G(-(p}eWMYl{l&Nb_4}7( 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 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json new file mode 100644 index 0000000..def13fd --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Operations Keyboard.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf new file mode 100644 index 0000000000000000000000000000000000000000..388605ae93a4e6040880d59df0fb53358f174c44 GIT binary patch literal 11395 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 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json new file mode 100644 index 0000000..68827d4 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Operations Symbol.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..312ca49f8dc65e40fcb9fa1de0ad96d363a2722c GIT binary patch literal 10709 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 literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json new file mode 100644 index 0000000..20a091b --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Sqrt with Power.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d7c21665a1418557fcf5fae5b03f0b0cc55f0e38 GIT binary patch literal 9434 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 U0`Yq)5g-M5Mp02MV{OL&0%{$Od;kCd literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json new file mode 100644 index 0000000..77856d4 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Sqrt.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf new file mode 100644 index 0000000000000000000000000000000000000000..32e996a477846c0aa2d5d7b9fdb6bf1e88971285 GIT binary patch literal 9498 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 ADF6Tf literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json new file mode 100644 index 0000000..465f18e --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Subscript.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf new file mode 100644 index 0000000000000000000000000000000000000000..37bbc58c3bfae7003a4f4f76cd4c785632d66afb GIT binary patch literal 9584 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 CiFc9! literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json new file mode 100644 index 0000000..9ad12a1 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Letter Symbol.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d51fa453570a9703a6fce8c62e4a5d03ac52f107 GIT binary patch literal 8197 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 MVEX#@?hdg30pRxj^Z)<= literal 0 HcmV?d00001 diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json new file mode 100644 index 0000000..68827d4 --- /dev/null +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Operations Symbol.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6a2a12d5f2a92e4aea2415c0ed4504b7aca110ec GIT binary patch literal 7918 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

FHdPF5ajM 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 literal 0 HcmV?d00001 From 8047cee270bfd37e3c24b5e9596ef555aac72a11 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 12:36:20 +0000 Subject: [PATCH 086/133] Match Swift extension API to ObjC MTView categories --- Package.swift | 9 +- .../internal/MTView/MTView+AutoLayout.m | 32 ------- .../internal/MTView/MTView+FirstResponder.m | 26 ------ mathEditor/internal/MTView/MTView+HitTest.m | 46 ---------- mathEditor/internal/MTView/MTView+Layout.m | 40 --------- mathEditorSwift/MTViewCategoryFunctions.swift | 86 +++++++++++++++++++ 6 files changed, 94 insertions(+), 145 deletions(-) delete mode 100644 mathEditor/internal/MTView/MTView+AutoLayout.m delete mode 100644 mathEditor/internal/MTView/MTView+FirstResponder.m delete mode 100644 mathEditor/internal/MTView/MTView+HitTest.m delete mode 100644 mathEditor/internal/MTView/MTView+Layout.m create mode 100644 mathEditorSwift/MTViewCategoryFunctions.swift diff --git a/Package.swift b/Package.swift index 9f1716e..bb13ad5 100644 --- a/Package.swift +++ b/Package.swift @@ -10,6 +10,9 @@ let package = Package( .library( name: "MathEditor", targets: ["MathEditor"]), + .library( + name: "MathEditorSwift", + targets: ["MathEditorSwift"]), .library( name: "MathKeyboard", targets: ["MathKeyboard"]), @@ -20,13 +23,17 @@ let package = Package( targets: [ .target( name: "MathEditor", - dependencies: [.product(name: "iosMath", package: "iosMath")], + dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditorSwift"], path: "./mathEditor", cSettings: [ .headerSearchPath("./editor"), .headerSearchPath("./internal"), ] ), + .target( + name: "MathEditorSwift", + path: "./mathEditorSwift" + ), .target( name: "MathKeyboard", dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditor"], diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.m b/mathEditor/internal/MTView/MTView+AutoLayout.m deleted file mode 100644 index be85c28..0000000 --- a/mathEditor/internal/MTView/MTView+AutoLayout.m +++ /dev/null @@ -1,32 +0,0 @@ -// -// MTView+AutoLayout.m -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import "MTConfig.h" -#import "MTView+AutoLayout.h" - -@implementation MXView (AutoLayout) - -- (void)pinToSuperview -{ - [self pinToSuperviewWithTop:0 leading:0 bottom:0 trailing:0]; -} - -- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing -{ - MTView *superview = self.superview; - if (!superview) - return; - self.translatesAutoresizingMaskIntoConstraints = NO; - [NSLayoutConstraint activateConstraints:@[ - [self.topAnchor constraintEqualToAnchor:superview.topAnchor constant:top], - [self.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor constant:leading], - [self.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor constant:-trailing], - [self.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor constant:-bottom] - ]]; -} - -@end diff --git a/mathEditor/internal/MTView/MTView+FirstResponder.m b/mathEditor/internal/MTView/MTView+FirstResponder.m deleted file mode 100644 index df4cda9..0000000 --- a/mathEditor/internal/MTView/MTView+FirstResponder.m +++ /dev/null @@ -1,26 +0,0 @@ -// -// MTView+FirstResponder.m -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import "MTConfig.h" -#import "MTView+FirstResponder.h" - -NS_ASSUME_NONNULL_BEGIN - -#if TARGET_OS_OSX - -@implementation NSView (FirstResponder) - -- (BOOL)isFirstResponder -{ - return self.window.firstResponder == self; -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+HitTest.m b/mathEditor/internal/MTView/MTView+HitTest.m deleted file mode 100644 index 59822b5..0000000 --- a/mathEditor/internal/MTView/MTView+HitTest.m +++ /dev/null @@ -1,46 +0,0 @@ -// -// MTView+HitTest.m -// MathEditor -// -// Created by Madiyar Aitbayev on 21/03/2026. -// - -#import "MTView+HitTest.h" - -#if TARGET_OS_OSX - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSView (HitTest) - -- (NSView *)hitTestOutsideBounds:(NSPoint)point -{ - return [self hitTestOutsideBounds:point ignoringSubviews:@[]]; -} - -- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews -{ - if (self.hidden) { - return nil; - } - NSPoint localPoint = [self convertPoint:point fromView:self.superview]; - for (NSView *child in [self.subviews reverseObjectEnumerator]) { - if ([ignoredSubviews containsObject:child]) { - continue; - } - NSView *hitView = [child hitTest:localPoint]; - if (hitView) { - return hitView; - } - } - if (NSPointInRect(localPoint, self.bounds)) { - return self; - } - return nil; -} - -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_OSX diff --git a/mathEditor/internal/MTView/MTView+Layout.m b/mathEditor/internal/MTView/MTView+Layout.m deleted file mode 100644 index 0d43c50..0000000 --- a/mathEditor/internal/MTView/MTView+Layout.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// MTView+Layout.m -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import "MTView+Layout.h" - -@implementation MXView (Layout) - -#if TARGET_OS_OSX - -- (void)setNeedsLayout -{ - [self setNeedsLayout:YES]; -} - -- (void)setNeedsDisplay -{ - [self setNeedsDisplay:YES]; -} - -- (void)layoutIfNeeded -{ - [self layoutSubtreeIfNeeded]; -} - -- (void)bringSubviewToFront:(NSView *)view -{ - if (view.superview != self) { - return; - } - [view removeFromSuperview]; - [self addSubview:view]; -} - -#endif - -@end diff --git a/mathEditorSwift/MTViewCategoryFunctions.swift b/mathEditorSwift/MTViewCategoryFunctions.swift new file mode 100644 index 0000000..8a59bd1 --- /dev/null +++ b/mathEditorSwift/MTViewCategoryFunctions.swift @@ -0,0 +1,86 @@ +#if canImport(AppKit) +import AppKit +public typealias MXView = NSView +#elseif canImport(UIKit) +import UIKit +public typealias MXView = UIView +#endif + +public extension MXView { + @objc(pinToSuperview) + func pinToSuperview() { + pinToSuperview(withTop: 0, leading: 0, bottom: 0, trailing: 0) + } + + @objc(pinToSuperviewWithTop:leading:bottom:trailing:) + func pinToSuperview(withTop top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { + guard let superview else { return } + translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + topAnchor.constraint(equalTo: superview.topAnchor, constant: top), + leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading), + trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -trailing), + bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom), + ]) + } +} + +#if canImport(AppKit) +public extension NSView { + @objc(setNeedsLayout) + func setNeedsLayout() { + setNeedsLayout(true) + } + + @objc(setNeedsDisplay) + func setNeedsDisplay() { + setNeedsDisplay(true) + } + + @objc(layoutIfNeeded) + func layoutIfNeeded() { + layoutSubtreeIfNeeded() + } + + @objc(bringSubviewToFront:) + func bringSubviewToFront(_ child: NSView) { + guard child.superview == self else { return } + child.removeFromSuperview() + addSubview(child) + } + + @objc(isFirstResponder) + var isFirstResponder: Bool { + window?.firstResponder == self + } + + @objc(hitTestOutsideBounds:) + func hitTestOutsideBounds(_ point: NSPoint) -> NSView? { + hitTestOutsideBounds(point, ignoringSubviews: []) + } + + @objc(hitTestOutsideBounds:ignoringSubviews:) + func hitTestOutsideBounds(_ point: NSPoint, ignoringSubviews: [NSView]) -> NSView? { + if isHidden { + return nil + } + + let localPoint = convert(point, from: superview) + for child in subviews.reversed() { + if ignoringSubviews.contains(child) { + continue + } + if let hitView = child.hitTest(localPoint) { + return hitView + } + } + + if bounds.contains(localPoint) { + return self + } + + return nil + } +} +#endif From 7b2f2097b7a52c8b361a776f90a470ef40eeb85a Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 13:09:20 +0000 Subject: [PATCH 087/133] Initial migration --- .../MathEditorSwiftUIExample.xcscheme | 78 +++++++++++++++++ Package.swift | 3 +- format.sh | 1 - .../editor/MTEditableMathLabel+NSView.m | 3 +- .../editor/MTEditableMathLabel+Responder.m | 3 +- mathEditor/editor/MTEditableMathLabel.m | 6 +- mathEditor/internal/MTCancelView.m | 3 +- mathEditor/internal/MTCaretView.m | 4 +- .../internal/MTView/MTView+AutoLayout.h | 20 ----- .../internal/MTView/MTView+FirstResponder.h | 22 ----- mathEditor/internal/MTView/MTView+HitTest.h | 23 ----- mathEditor/internal/MTView/MTView+Layout.h | 28 ------ mathEditor/internal/MTView/MXView.h | 14 --- .../Internal/MTView/MTView+AutoLayout.swift | 34 ++++++++ mathEditorSwift/Internal/MTView/MTView.swift | 14 +++ .../MTView/NSView+FirstResponder.swift | 18 ++++ .../Internal/MTView/NSView+HitTest.swift | 38 ++++++++ .../Internal/MTView/NSView+Layout.swift | 34 ++++++++ mathEditorSwift/MTViewCategoryFunctions.swift | 86 ------------------- 19 files changed, 228 insertions(+), 204 deletions(-) create mode 100644 MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme delete mode 100644 mathEditor/internal/MTView/MTView+AutoLayout.h delete mode 100644 mathEditor/internal/MTView/MTView+FirstResponder.h delete mode 100644 mathEditor/internal/MTView/MTView+HitTest.h delete mode 100644 mathEditor/internal/MTView/MTView+Layout.h delete mode 100644 mathEditor/internal/MTView/MXView.h create mode 100644 mathEditorSwift/Internal/MTView/MTView+AutoLayout.swift create mode 100644 mathEditorSwift/Internal/MTView/MTView.swift create mode 100644 mathEditorSwift/Internal/MTView/NSView+FirstResponder.swift create mode 100644 mathEditorSwift/Internal/MTView/NSView+HitTest.swift create mode 100644 mathEditorSwift/Internal/MTView/NSView+Layout.swift delete mode 100644 mathEditorSwift/MTViewCategoryFunctions.swift diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme new file mode 100644 index 0000000..f7ab85f --- /dev/null +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/xcshareddata/xcschemes/MathEditorSwiftUIExample.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index bb13ad5..0039460 100644 --- a/Package.swift +++ b/Package.swift @@ -32,7 +32,8 @@ let package = Package( ), .target( name: "MathEditorSwift", - path: "./mathEditorSwift" + dependencies: [.product(name: "iosMath", package: "iosMath")], + path: "./mathEditorSwift", ), .target( name: "MathKeyboard", diff --git a/format.sh b/format.sh index ede1b3c..0de1175 100755 --- a/format.sh +++ b/format.sh @@ -24,4 +24,3 @@ lint() { format . lint . -clang_format ./mathEditor/internal/MTView diff --git a/mathEditor/editor/MTEditableMathLabel+NSView.m b/mathEditor/editor/MTEditableMathLabel+NSView.m index 9bfa91e..95252e5 100644 --- a/mathEditor/editor/MTEditableMathLabel+NSView.m +++ b/mathEditor/editor/MTEditableMathLabel+NSView.m @@ -9,7 +9,8 @@ #import "MTEditableMathLabel.h" #import "MTMathUILabel.h" -#import "MTView/MTView+HitTest.h" + +@import MathEditorSwift; NS_ASSUME_NONNULL_BEGIN diff --git a/mathEditor/editor/MTEditableMathLabel+Responder.m b/mathEditor/editor/MTEditableMathLabel+Responder.m index 0e3e4d7..968bc00 100644 --- a/mathEditor/editor/MTEditableMathLabel+Responder.m +++ b/mathEditor/editor/MTEditableMathLabel+Responder.m @@ -8,7 +8,8 @@ #import #import "MTConfig.h" #import "MTEditableMathLabel.h" -#import "MTView/MTView+FirstResponder.h" + +@import MathEditorSwift; NS_ASSUME_NONNULL_BEGIN diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index c0c757b..251f157 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -19,13 +19,11 @@ #import "MTTapGestureRecognizer.h" #import "MTMathList+Editing.h" #import "MTDisplay+Editing.h" -#import "MTView/MTView+AutoLayout.h" -#import "MTView/MTView+Layout.h" -#import "MTView/MTView+FirstResponder.h" - #import "MTUnicode.h" #import "MTMathListBuilder.h" +@import MathEditorSwift; + @interface MTEditableMathLabel() @property (nonatomic) MTMathUILabel* label; diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m index 9d40ec9..bc704bb 100644 --- a/mathEditor/internal/MTCancelView.m +++ b/mathEditor/internal/MTCancelView.m @@ -6,7 +6,8 @@ #import "MTCancelView.h" #import "MTTapGestureRecognizer.h" -#import "MTView/MTView+AutoLayout.h" + +@import MathEditorSwift; @interface MTCancelView () diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index 1e7b041..01d08ac 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -11,10 +11,10 @@ #import "MTCaretView.h" #import "MTEditableMathLabel.h" #import "MTConfig.h" -#import "MTView/MTView+Layout.h" -#import "MTView/MTView+HitTest.h" #import "NSBezierPath+addLineToPoint.h" +@import MathEditorSwift; + static const NSTimeInterval InitialBlinkDelay = 0.7; static const NSTimeInterval BlinkRate = 0.5; // The settings below make sense for the given font size. They are scaled appropriately when the fontsize changes. diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.h b/mathEditor/internal/MTView/MTView+AutoLayout.h deleted file mode 100644 index 62cf81b..0000000 --- a/mathEditor/internal/MTView/MTView+AutoLayout.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// MTView+AutoLayout.h -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import "MXView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface MXView (AutoLayout) - -- (void)pinToSuperview; - -- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing; - -@end - -NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+FirstResponder.h b/mathEditor/internal/MTView/MTView+FirstResponder.h deleted file mode 100644 index 67a2b94..0000000 --- a/mathEditor/internal/MTView/MTView+FirstResponder.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// MTView+FirstResponder.h -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import "MTConfig.h" - -NS_ASSUME_NONNULL_BEGIN - -#if TARGET_OS_OSX - -@interface NSView (FirstResponder) - -@property (nonatomic, readonly) BOOL isFirstResponder; - -@end - -#endif // TARGET_OS_OSX - -NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+HitTest.h b/mathEditor/internal/MTView/MTView+HitTest.h deleted file mode 100644 index 0c87d63..0000000 --- a/mathEditor/internal/MTView/MTView+HitTest.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MTView+HitTest.h -// MathEditor -// -// Created by Madiyar Aitbayev on 21/03/2026. -// - -#if TARGET_OS_OSX - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSView (HitTest) - -- (NSView *)hitTestOutsideBounds:(NSPoint)point; -- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews; - -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_OSX diff --git a/mathEditor/internal/MTView/MTView+Layout.h b/mathEditor/internal/MTView/MTView+Layout.h deleted file mode 100644 index fdeb22b..0000000 --- a/mathEditor/internal/MTView/MTView+Layout.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// MTView+Layout.h -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#import "MXView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface MXView (Layout) - -#if TARGET_OS_OSX - -- (void)setNeedsLayout; - -- (void)setNeedsDisplay; - -- (void)layoutIfNeeded; - -- (void)bringSubviewToFront:(NSView *)view; - -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MXView.h b/mathEditor/internal/MTView/MXView.h deleted file mode 100644 index d781e72..0000000 --- a/mathEditor/internal/MTView/MXView.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// MXView.h -// MathEditor -// -// Created by Madiyar Aitbayev on 20/03/2026. -// - -#if TARGET_OS_OSX -#import -#define MXView NSView -#else -#import -#define MXView UIView -#endif diff --git a/mathEditorSwift/Internal/MTView/MTView+AutoLayout.swift b/mathEditorSwift/Internal/MTView/MTView+AutoLayout.swift new file mode 100644 index 0000000..03ab73e --- /dev/null +++ b/mathEditorSwift/Internal/MTView/MTView+AutoLayout.swift @@ -0,0 +1,34 @@ +// +// MTView+AutoLayout.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 24/03/2026. +// + +import Foundation + +#if canImport(AppKit) + import AppKit +#elseif canImport(UIKit) + import UIKit +#endif + +extension MTView { + @objc public func pinToSuperview() { + pinToSuperview(withTop: 0, leading: 0, bottom: 0, trailing: 0) + } + + @objc public func pinToSuperview( + withTop top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat + ) { + guard let superview else { return } + translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + topAnchor.constraint(equalTo: superview.topAnchor, constant: top), + leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading), + trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -trailing), + bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom), + ]) + } +} diff --git a/mathEditorSwift/Internal/MTView/MTView.swift b/mathEditorSwift/Internal/MTView/MTView.swift new file mode 100644 index 0000000..2eca458 --- /dev/null +++ b/mathEditorSwift/Internal/MTView/MTView.swift @@ -0,0 +1,14 @@ +// +// MTView.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 24/03/2026. +// + +#if canImport(AppKit) + import AppKit + public typealias MTView = NSView +#elseif canImport(UIKit) + import UIKit + public typealias MTView = UIView +#endif diff --git a/mathEditorSwift/Internal/MTView/NSView+FirstResponder.swift b/mathEditorSwift/Internal/MTView/NSView+FirstResponder.swift new file mode 100644 index 0000000..c7f15d5 --- /dev/null +++ b/mathEditorSwift/Internal/MTView/NSView+FirstResponder.swift @@ -0,0 +1,18 @@ +// +// MTView+FirstResponder.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 24/03/2026. +// + +#if os(macOS) + + import AppKit + + extension NSView { + @objc public var isFirstResponder: Bool { + window?.firstResponder == self + } + } + +#endif diff --git a/mathEditorSwift/Internal/MTView/NSView+HitTest.swift b/mathEditorSwift/Internal/MTView/NSView+HitTest.swift new file mode 100644 index 0000000..32ffa20 --- /dev/null +++ b/mathEditorSwift/Internal/MTView/NSView+HitTest.swift @@ -0,0 +1,38 @@ +// +// MTView+HitTest.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 24/03/2026. +// + +#if canImport(AppKit) + import AppKit + extension NSView { + @objc public func hitTestOutsideBounds(_ point: NSPoint) -> NSView? { + hitTestOutsideBounds(point, ignoringSubviews: []) + } + + @objc public func hitTestOutsideBounds(_ point: NSPoint, ignoringSubviews: [NSView]) -> NSView? + { + if isHidden { + return nil + } + + let localPoint = convert(point, from: superview) + for child in subviews.reversed() { + if ignoringSubviews.contains(child) { + continue + } + if let hitView = child.hitTest(localPoint) { + return hitView + } + } + + if bounds.contains(localPoint) { + return self + } + + return nil + } + } +#endif diff --git a/mathEditorSwift/Internal/MTView/NSView+Layout.swift b/mathEditorSwift/Internal/MTView/NSView+Layout.swift new file mode 100644 index 0000000..848d492 --- /dev/null +++ b/mathEditorSwift/Internal/MTView/NSView+Layout.swift @@ -0,0 +1,34 @@ +// +// NSView+Layout.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 24/03/2026. +// + +#if canImport(AppKit) + import AppKit + + extension NSView { + @objc(setNeedsLayout) + public func setNeedsLayout() { + self.needsDisplay = true + } + + @objc(setNeedsDisplay) + public func setNeedsDisplay() { + self.needsDisplay = true + } + + @objc(layoutIfNeeded) + public func layoutIfNeeded() { + layoutSubtreeIfNeeded() + } + + @objc(bringSubviewToFront:) + public func bringSubviewToFront(_ child: NSView) { + guard child.superview == self else { return } + child.removeFromSuperview() + addSubview(child) + } + } +#endif diff --git a/mathEditorSwift/MTViewCategoryFunctions.swift b/mathEditorSwift/MTViewCategoryFunctions.swift deleted file mode 100644 index 8a59bd1..0000000 --- a/mathEditorSwift/MTViewCategoryFunctions.swift +++ /dev/null @@ -1,86 +0,0 @@ -#if canImport(AppKit) -import AppKit -public typealias MXView = NSView -#elseif canImport(UIKit) -import UIKit -public typealias MXView = UIView -#endif - -public extension MXView { - @objc(pinToSuperview) - func pinToSuperview() { - pinToSuperview(withTop: 0, leading: 0, bottom: 0, trailing: 0) - } - - @objc(pinToSuperviewWithTop:leading:bottom:trailing:) - func pinToSuperview(withTop top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { - guard let superview else { return } - translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - topAnchor.constraint(equalTo: superview.topAnchor, constant: top), - leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading), - trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -trailing), - bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom), - ]) - } -} - -#if canImport(AppKit) -public extension NSView { - @objc(setNeedsLayout) - func setNeedsLayout() { - setNeedsLayout(true) - } - - @objc(setNeedsDisplay) - func setNeedsDisplay() { - setNeedsDisplay(true) - } - - @objc(layoutIfNeeded) - func layoutIfNeeded() { - layoutSubtreeIfNeeded() - } - - @objc(bringSubviewToFront:) - func bringSubviewToFront(_ child: NSView) { - guard child.superview == self else { return } - child.removeFromSuperview() - addSubview(child) - } - - @objc(isFirstResponder) - var isFirstResponder: Bool { - window?.firstResponder == self - } - - @objc(hitTestOutsideBounds:) - func hitTestOutsideBounds(_ point: NSPoint) -> NSView? { - hitTestOutsideBounds(point, ignoringSubviews: []) - } - - @objc(hitTestOutsideBounds:ignoringSubviews:) - func hitTestOutsideBounds(_ point: NSPoint, ignoringSubviews: [NSView]) -> NSView? { - if isHidden { - return nil - } - - let localPoint = convert(point, from: superview) - for child in subviews.reversed() { - if ignoringSubviews.contains(child) { - continue - } - if let hitView = child.hitTest(localPoint) { - return hitView - } - } - - if bounds.contains(localPoint) { - return self - } - - return nil - } -} -#endif From efee7f71e91376ab8651bec11a612956aace882a Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 14:10:05 +0000 Subject: [PATCH 088/133] Move editing internals back from mathEditorSwiftUI and drop shim indirection --- MathEditor.xcodeproj/project.pbxproj | 2 - mathEditor/editor/MTEditableMathLabel.m | 1 - mathEditor/internal/MTCancelView.h | 13 ----- mathEditor/internal/MTCancelView.m | 51 ------------------- mathEditorSwift/Internal/MTCancelView.swift | 47 +++++++++++++++++ .../Internal/MTTapGestureRecognizer.swift | 9 ++++ mathEditorSwift/Internal/MTTypes.swift | 17 +++++++ 7 files changed, 73 insertions(+), 67 deletions(-) delete mode 100644 mathEditor/internal/MTCancelView.h delete mode 100644 mathEditor/internal/MTCancelView.m create mode 100644 mathEditorSwift/Internal/MTCancelView.swift create mode 100644 mathEditorSwift/Internal/MTTapGestureRecognizer.swift create mode 100644 mathEditorSwift/Internal/MTTypes.swift diff --git a/MathEditor.xcodeproj/project.pbxproj b/MathEditor.xcodeproj/project.pbxproj index 593056d..2abfebe 100644 --- a/MathEditor.xcodeproj/project.pbxproj +++ b/MathEditor.xcodeproj/project.pbxproj @@ -47,7 +47,6 @@ 490BE5561CE695CC00AE31A0 /* libMathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMathEditor.a; sourceTree = BUILT_PRODUCTS_DIR; }; 490BE5771CE6A08100AE31A0 /* MTDisplayEditingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTDisplayEditingTest.m; sourceTree = ""; }; 497F92211CF8CBFF00022162 /* MathEditor-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MathEditor-Prefix.pch"; sourceTree = ""; }; - 49DEC81E1CF5523E000053CD /* MTCaretView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTCaretView.h; sourceTree = ""; }; 49DEC81F1CF5523E000053CD /* MTCaretView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTCaretView.m; sourceTree = ""; }; 49DEC8201CF5523E000053CD /* MTDisplay+Editing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTDisplay+Editing.h"; sourceTree = ""; }; 49DEC8211CF5523E000053CD /* MTDisplay+Editing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTDisplay+Editing.m"; sourceTree = ""; }; @@ -136,7 +135,6 @@ 49DEC81D1CF5523E000053CD /* internal */ = { isa = PBXGroup; children = ( - 49DEC81E1CF5523E000053CD /* MTCaretView.h */, 49DEC81F1CF5523E000053CD /* MTCaretView.m */, 49DEC8201CF5523E000053CD /* MTDisplay+Editing.h */, 49DEC8211CF5523E000053CD /* MTDisplay+Editing.m */, diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 251f157..856e796 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -14,7 +14,6 @@ #import "MTMathList.h" #import "MTMathUILabel.h" #import "MTMathAtomFactory.h" -#import "MTCancelView.h" #import "MTCaretView.h" #import "MTTapGestureRecognizer.h" #import "MTMathList+Editing.h" diff --git a/mathEditor/internal/MTCancelView.h b/mathEditor/internal/MTCancelView.h deleted file mode 100644 index 8d243c6..0000000 --- a/mathEditor/internal/MTCancelView.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// MTCancelView.h -// -// Created for the editable label clear affordance. -// - -@import iosMath; - -@interface MTCancelView : MTView - -- (instancetype)initWithTarget:(id)target action:(SEL)action; - -@end diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m deleted file mode 100644 index bc704bb..0000000 --- a/mathEditor/internal/MTCancelView.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// MTCancelView.m -// -// Created for the editable label clear affordance. -// - -#import "MTCancelView.h" -#import "MTTapGestureRecognizer.h" - -@import MathEditorSwift; - -@interface MTCancelView () - -#if TARGET_OS_IPHONE -@property (nonatomic, strong) UIImageView *imageView; -#else -@property (nonatomic, strong) NSImageView *imageView; -#endif - -@end - -@implementation MTCancelView - -- (instancetype)initWithTarget:(id)target action:(SEL)action -{ - self = [super initWithFrame:CGRectZero]; - if (self) { -#if TARGET_OS_IPHONE - UIImage *image = [UIImage systemImageNamed:@"xmark.circle"]; - image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - _imageView = [[UIImageView alloc] initWithImage:image]; - _imageView.contentMode = UIViewContentModeScaleAspectFit; - _imageView.tintColor = [MTColor secondaryLabelColor]; -#else - NSImage *image = [NSImage imageWithSystemSymbolName:@"xmark.circle" accessibilityDescription:nil]; - _imageView = [[NSImageView alloc] initWithFrame:CGRectZero]; - _imageView.image = image; - _imageView.imageScaling = NSImageScaleProportionallyUpOrDown; - _imageView.contentTintColor = [MTColor secondaryLabelColor]; -#endif - [self addSubview:_imageView]; - [_imageView pinToSuperview]; - - MTTapGestureRecognizer *tapRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:target action:action]; - [self addGestureRecognizer:tapRecognizer]; - self.hidden = YES; - } - return self; -} - -@end diff --git a/mathEditorSwift/Internal/MTCancelView.swift b/mathEditorSwift/Internal/MTCancelView.swift new file mode 100644 index 0000000..090b771 --- /dev/null +++ b/mathEditorSwift/Internal/MTCancelView.swift @@ -0,0 +1,47 @@ +import Foundation + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +@objc(MTCancelView) +public final class MTCancelView: MTView { + private let imageView: MTImageView + + @objc(initWithTarget:action:) + public init(target: AnyObject, action: Selector) { + #if canImport(UIKit) + let image = MTImage(systemName: "xmark.circle")?.withRenderingMode(.alwaysTemplate) + imageView = MTImageView(image: image) + imageView.contentMode = .scaleAspectFit + imageView.tintColor = .secondaryLabel + #else + imageView = MTImageView(frame: .zero) + imageView.image = MTImage(systemSymbolName: "xmark.circle", accessibilityDescription: nil) + imageView.imageScaling = .scaleProportionallyUpOrDown + imageView.contentTintColor = .secondaryLabelColor + #endif + + super.init(frame: .zero) + + addSubview(imageView) + imageView.pinToSuperview() + + #if canImport(UIKit) + let tap = UITapGestureRecognizer(target: target, action: action) + addGestureRecognizer(tap) + #else + let tap = NSClickGestureRecognizer(target: target, action: action) + addGestureRecognizer(tap) + #endif + + isHidden = true + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/mathEditorSwift/Internal/MTTapGestureRecognizer.swift b/mathEditorSwift/Internal/MTTapGestureRecognizer.swift new file mode 100644 index 0000000..20bf011 --- /dev/null +++ b/mathEditorSwift/Internal/MTTapGestureRecognizer.swift @@ -0,0 +1,9 @@ +import Foundation + +#if canImport(UIKit) +import UIKit +public typealias MTTapGestureRecognizer = UITapGestureRecognizer +#elseif canImport(AppKit) +import AppKit +public typealias MTTapGestureRecognizer = NSClickGestureRecognizer +#endif diff --git a/mathEditorSwift/Internal/MTTypes.swift b/mathEditorSwift/Internal/MTTypes.swift new file mode 100644 index 0000000..38b28a2 --- /dev/null +++ b/mathEditorSwift/Internal/MTTypes.swift @@ -0,0 +1,17 @@ +import Foundation + +#if canImport(UIKit) +import UIKit + +public typealias MTColor = UIColor +public typealias MTBezierPath = UIBezierPath +public typealias MTImage = UIImage +public typealias MTImageView = UIImageView +#elseif canImport(AppKit) +import AppKit + +public typealias MTColor = NSColor +public typealias MTBezierPath = NSBezierPath +public typealias MTImage = NSImage +public typealias MTImageView = NSImageView +#endif From 283a635b37be3ebf583ef8b6b2531092ba01ab35 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 14:18:49 +0000 Subject: [PATCH 089/133] MTCancelView in SwiftUI --- mathEditor/editor/MTEditableMathLabel.m | 2 +- mathEditorSwift/Internal/MTCancelView.swift | 18 +++------------- mathEditorSwift/Internal/MTTypes.swift | 17 --------------- mathEditorSwift/Internal/MTView/MTView.swift | 22 ++++++++++++++------ 4 files changed, 20 insertions(+), 39 deletions(-) delete mode 100644 mathEditorSwift/Internal/MTTypes.swift diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 856e796..fe6bfde 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -84,7 +84,7 @@ - (void) initialize #endif label.textAlignment = kMTTextAlignmentCenter; self.label = label; - // [self createCancelImage]; + [self createCancelImage]; CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height); _flipTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, -1.0), transform); diff --git a/mathEditorSwift/Internal/MTCancelView.swift b/mathEditorSwift/Internal/MTCancelView.swift index 090b771..50ac9fd 100644 --- a/mathEditorSwift/Internal/MTCancelView.swift +++ b/mathEditorSwift/Internal/MTCancelView.swift @@ -1,16 +1,10 @@ import Foundation -#if canImport(UIKit) -import UIKit -#elseif canImport(AppKit) -import AppKit -#endif - -@objc(MTCancelView) +@objc public final class MTCancelView: MTView { private let imageView: MTImageView - @objc(initWithTarget:action:) + @objc public init(target: AnyObject, action: Selector) { #if canImport(UIKit) let image = MTImage(systemName: "xmark.circle")?.withRenderingMode(.alwaysTemplate) @@ -29,13 +23,7 @@ public final class MTCancelView: MTView { addSubview(imageView) imageView.pinToSuperview() - #if canImport(UIKit) - let tap = UITapGestureRecognizer(target: target, action: action) - addGestureRecognizer(tap) - #else - let tap = NSClickGestureRecognizer(target: target, action: action) - addGestureRecognizer(tap) - #endif + addGestureRecognizer(MTTapGestureRecognizer(target: target, action: action)) isHidden = true } diff --git a/mathEditorSwift/Internal/MTTypes.swift b/mathEditorSwift/Internal/MTTypes.swift deleted file mode 100644 index 38b28a2..0000000 --- a/mathEditorSwift/Internal/MTTypes.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -#if canImport(UIKit) -import UIKit - -public typealias MTColor = UIColor -public typealias MTBezierPath = UIBezierPath -public typealias MTImage = UIImage -public typealias MTImageView = UIImageView -#elseif canImport(AppKit) -import AppKit - -public typealias MTColor = NSColor -public typealias MTBezierPath = NSBezierPath -public typealias MTImage = NSImage -public typealias MTImageView = NSImageView -#endif diff --git a/mathEditorSwift/Internal/MTView/MTView.swift b/mathEditorSwift/Internal/MTView/MTView.swift index 2eca458..167db3b 100644 --- a/mathEditorSwift/Internal/MTView/MTView.swift +++ b/mathEditorSwift/Internal/MTView/MTView.swift @@ -5,10 +5,20 @@ // Created by Madiyar Aitbayev on 24/03/2026. // -#if canImport(AppKit) - import AppKit - public typealias MTView = NSView -#elseif canImport(UIKit) - import UIKit - public typealias MTView = UIView +import Foundation + +#if canImport(UIKit) +import UIKit +public typealias MTView = UIView +public typealias MTColor = UIColor +public typealias MTBezierPath = UIBezierPath +public typealias MTImage = UIImage +public typealias MTImageView = UIImageView +#elseif canImport(AppKit) +import AppKit +public typealias MTView = NSView +public typealias MTColor = NSColor +public typealias MTBezierPath = NSBezierPath +public typealias MTImage = NSImage +public typealias MTImageView = NSImageView #endif From cd325e63f6dacb58a8bbc505e7f7d121dd0e71b8 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 14:24:53 +0000 Subject: [PATCH 090/133] Migrate MTMathList editing category to Swift --- mathEditor/internal/MTMathList+Editing.m | 286 ------------------ .../Internal/MTMathList+Editing.swift | 237 +++++++++++++++ 2 files changed, 237 insertions(+), 286 deletions(-) delete mode 100644 mathEditor/internal/MTMathList+Editing.m create mode 100644 mathEditorSwift/Internal/MTMathList+Editing.swift diff --git a/mathEditor/internal/MTMathList+Editing.m b/mathEditor/internal/MTMathList+Editing.m deleted file mode 100644 index a7a22e5..0000000 --- a/mathEditor/internal/MTMathList+Editing.m +++ /dev/null @@ -1,286 +0,0 @@ -// -// MTMathList+Editing.m -// -// Created by Kostub Deshmukh on 9/5/13. -// Copyright (C) 2013 MathChat -// -// This software may be modified and distributed under the terms of the -// MIT license. See the LICENSE file for details. -// - -#import "MTMathList+Editing.h" - -#pragma mark - MTMathList - -@implementation MTMathList (Editing) - -- (void)insertAtom:(MTMathAtom *)atom atListIndex:(MTMathListIndex *)index -{ - if (index.atomIndex > self.atoms.count) { - @throw [NSException exceptionWithName:NSRangeException - reason:[NSString stringWithFormat:@"Index %lu is out of bounds for list of size %lu", (unsigned long)index.atomIndex, (unsigned long)self.atoms.count] - userInfo:nil]; - } - - switch (index.subIndexType) { - case kMTSubIndexTypeNone: - [self insertAtom:atom atIndex:index.atomIndex]; - break; - - case kMTSubIndexTypeNucleus: { - MTMathAtom* currentAtom = [self.atoms objectAtIndex:index.atomIndex]; - NSAssert(currentAtom.subScript || currentAtom.superScript, @"Nuclear fusion is not supported if there are no subscripts or superscripts."); - NSAssert(!atom.subScript && !atom.superScript, @"Cannot fuse with an atom that already has a subscript or a superscript"); - - atom.subScript = currentAtom.subScript; - atom.superScript = currentAtom.superScript; - currentAtom.subScript = nil; - currentAtom.superScript = nil; - [self insertAtom:atom atIndex:index.atomIndex + index.subIndex.atomIndex]; - break; - } - - case kMTSubIndexTypeDegree: - case kMTSubIndexTypeRadicand: { - MTRadical *radical = [self.atoms objectAtIndex:index.atomIndex]; - if (!radical || radical.type != kMTMathAtomRadical) { - // Not radical, quit - NSAssert(false, @"No radical found at index %lu", (unsigned long)index.atomIndex); - return; - } - if (index.subIndexType == kMTSubIndexTypeDegree) { - [radical.degree insertAtom:atom atListIndex:index.subIndex]; - } else { - [radical.radicand insertAtom:atom atListIndex:index.subIndex]; - } - break; - } - - case kMTSubIndexTypeDenominator: - case kMTSubIndexTypeNumerator: { - MTFraction* frac = [self.atoms objectAtIndex:index.atomIndex]; - if (!frac || frac.type != kMTMathAtomFraction) { - NSAssert(false, @"No fraction found at index %lu", (unsigned long)index.atomIndex); - return; - } - if (index.subIndexType == kMTSubIndexTypeNumerator) { - [frac.numerator insertAtom:atom atListIndex:index.subIndex]; - } else { - [frac.denominator insertAtom:atom atListIndex:index.subIndex]; - } - break; - } - - case kMTSubIndexTypeSubscript: { - MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; - NSAssert(current.subScript, @"No subscript for atom at index %lu", (unsigned long)index.atomIndex); - [current.subScript insertAtom:atom atListIndex:index.subIndex]; - break; - } - - case kMTSubIndexTypeSuperscript: { - MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; - NSAssert(current.superScript, @"No superscript for atom at index %lu", (unsigned long)index.atomIndex); - [current.superScript insertAtom:atom atListIndex:index.subIndex]; - break; - } - } -} - --(void)removeAtomAtListIndex:(MTMathListIndex *)index -{ - if (index.atomIndex >= self.atoms.count) { - @throw [NSException exceptionWithName:NSRangeException - reason:[NSString stringWithFormat:@"Index %lu is out of bounds for list of size %lu", (unsigned long)index.atomIndex, (unsigned long)self.atoms.count] - userInfo:nil]; - } - - switch (index.subIndexType) { - case kMTSubIndexTypeNone: - [self removeAtomAtIndex:index.atomIndex]; - break; - - case kMTSubIndexTypeNucleus: { - MTMathAtom* currentAtom = [self.atoms objectAtIndex:index.atomIndex]; - NSAssert(currentAtom.subScript || currentAtom.superScript, @"Nuclear fission is not supported if there are no subscripts or superscripts."); - MTMathAtom* previous = nil; - if (index.atomIndex > 0) { - previous = [self.atoms objectAtIndex:index.atomIndex - 1]; - } - if (previous && !previous.subScript && !previous.superScript) { - previous.superScript = currentAtom.superScript; - previous.subScript = currentAtom.subScript; - [self removeAtomAtIndex:index.atomIndex]; - } else { - // no previous atom or the previous atom sucks (has sub/super scripts) - currentAtom.nucleus = @""; - } - break; - } - - case kMTSubIndexTypeRadicand: - case kMTSubIndexTypeDegree: { - MTRadical *radical = [self.atoms objectAtIndex:index.atomIndex]; - if (!radical || radical.type != kMTMathAtomRadical) { - // Not radical, quit - NSAssert(false, @"No radical found at index %lu", (unsigned long)index.atomIndex); - return; - } - if (index.subIndexType == kMTSubIndexTypeDegree) { - [radical.degree removeAtomAtListIndex:index.subIndex]; - } else { - [radical.radicand removeAtomAtListIndex:index.subIndex]; - } - - break; - } - - case kMTSubIndexTypeDenominator: - case kMTSubIndexTypeNumerator: { - MTFraction* frac = [self.atoms objectAtIndex:index.atomIndex]; - if (!frac || frac.type != kMTMathAtomFraction) { - NSAssert(false, @"No fraction found at index %lu", (unsigned long)index.atomIndex); - return; - } - if (index.subIndexType == kMTSubIndexTypeNumerator) { - [frac.numerator removeAtomAtListIndex:index.subIndex]; - } else { - [frac.denominator removeAtomAtListIndex:index.subIndex]; - } - break; - } - - case kMTSubIndexTypeSubscript: { - MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; - NSAssert(current.subScript, @"No subscript for atom at index %lu", (unsigned long)index.atomIndex); - [current.subScript removeAtomAtListIndex:index.subIndex]; - break; - } - - case kMTSubIndexTypeSuperscript: { - MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; - NSAssert(current.superScript, @"No superscript for atom at index %lu", (unsigned long)index.atomIndex); - [current.superScript removeAtomAtListIndex:index.subIndex]; - break; - } - } -} - -- (void) removeAtomsInListIndexRange:(MTMathListRange*) range -{ - MTMathListIndex* start = range.start; - - switch (start.subIndexType) { - case kMTSubIndexTypeNone: - [self removeAtomsInRange:NSMakeRange(start.atomIndex, range.length)]; - break; - - case kMTSubIndexTypeNucleus: - NSAssert(false, @"Nuclear fission is not supported"); - break; - - case kMTSubIndexTypeRadicand: - case kMTSubIndexTypeDegree: { - MTRadical *radical = [self.atoms objectAtIndex:start.atomIndex]; - if (!radical || radical.type != kMTMathAtomRadical) { - // Not radical, quit - NSAssert(false, @"No radical found at index %lu", (unsigned long)start.atomIndex); - return; - } - if (start.subIndexType == kMTSubIndexTypeDegree) { - [radical.degree removeAtomsInListIndexRange:range.subIndexRange]; - } else { - [radical.radicand removeAtomsInListIndexRange:range.subIndexRange]; - } - break; - } - - case kMTSubIndexTypeDenominator: - case kMTSubIndexTypeNumerator: { - MTFraction* frac = [self.atoms objectAtIndex:start.atomIndex]; - if (!frac || frac.type != kMTMathAtomFraction) { - NSAssert(false, @"No fraction found at index %lu", (unsigned long)start.atomIndex); - return; - } - if (start.subIndexType == kMTSubIndexTypeNumerator) { - [frac.numerator removeAtomsInListIndexRange:range.subIndexRange]; - } else { - [frac.denominator removeAtomsInListIndexRange:range.subIndexRange]; - } - break; - } - - case kMTSubIndexTypeSubscript: { - MTMathAtom* current = [self.atoms objectAtIndex:start.atomIndex]; - NSAssert(current.subScript, @"No subscript for atom at index %lu", (unsigned long)start.atomIndex); - [current.subScript removeAtomsInListIndexRange:range.subIndexRange]; - break; - } - - case kMTSubIndexTypeSuperscript: { - MTMathAtom* current = [self.atoms objectAtIndex:start.atomIndex]; - NSAssert(current.superScript, @"No superscript for atom at index %lu", (unsigned long)start.atomIndex); - [current.superScript removeAtomsInListIndexRange:range.subIndexRange]; - break; - } - } -} - -- (MTMathAtom *)atomAtListIndex:(MTMathListIndex *)index -{ - if (index == nil) { - return nil; - } - - if (index.atomIndex >= self.atoms.count) { - return nil; - } - - MTMathAtom* atom = self.atoms[index.atomIndex]; - - switch (index.subIndexType) { - case kMTSubIndexTypeNone: - case kMTSubIndexTypeNucleus: - return atom; - - case kMTSubIndexTypeSubscript: - return [atom.subScript atomAtListIndex:index.subIndex]; - - case kMTSubIndexTypeSuperscript: - return [atom.superScript atomAtListIndex:index.subIndex]; - - case kMTSubIndexTypeRadicand: - case kMTSubIndexTypeDegree: { - if (atom.type == kMTMathAtomRadical) { - MTRadical *radical = (MTRadical *) atom; - if (index.subIndexType == kMTSubIndexTypeDegree) { - return [radical.degree atomAtListIndex:index.subIndex]; - } else { - return [radical.radicand atomAtListIndex:index.subIndex]; - } - } else { - // No radical at this index - return nil; - } - } - - case kMTSubIndexTypeNumerator: - case kMTSubIndexTypeDenominator: - { - if (atom.type == kMTMathAtomFraction) { - MTFraction* frac = (MTFraction*) atom; - if (index.subIndexType == kMTSubIndexTypeDenominator) { - return [frac.denominator atomAtListIndex:index.subIndex]; - } else { - return [frac.numerator atomAtListIndex:index.subIndex]; - } - - } else { - // No fraction at this index. - return nil; - } - } - } -} - -@end diff --git a/mathEditorSwift/Internal/MTMathList+Editing.swift b/mathEditorSwift/Internal/MTMathList+Editing.swift new file mode 100644 index 0000000..74dd70b --- /dev/null +++ b/mathEditorSwift/Internal/MTMathList+Editing.swift @@ -0,0 +1,237 @@ +// +// MTMathList+Editing.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 24/03/2026. +// + +import Foundation +import iosMath + +extension MTMathList { + @objc(insertAtom:atListIndex:) + public func insert(_ atom: MTMathAtom, atListIndex index: MTMathListIndex) { + if index.atomIndex > atoms.count { + let exception = NSException( + name: .rangeException, + reason: "Index \(index.atomIndex) is out of bounds for list of size \(atoms.count)", + userInfo: nil + ) + exception.raise() + return + } + + switch index.subIndexType { + case .none: + insert(atom, at: index.atomIndex) + + case .nucleus: + guard let currentAtom = atoms[index.atomIndex] as? MTMathAtom else { return } + assert(currentAtom.subScript != nil || currentAtom.superScript != nil, + "Nuclear fusion is not supported if there are no subscripts or superscripts.") + assert(atom.subScript == nil && atom.superScript == nil, + "Cannot fuse with an atom that already has a subscript or a superscript") + + atom.subScript = currentAtom.subScript + atom.superScript = currentAtom.superScript + currentAtom.subScript = nil + currentAtom.superScript = nil + insert(atom, at: index.atomIndex + index.subIndex.atomIndex) + + case .degree, .radicand: + guard let radical = atoms[index.atomIndex] as? MTRadical, radical.type == .radical else { + assertionFailure("No radical found at index \(index.atomIndex)") + return + } + if index.subIndexType == .degree { + radical.degree.insert(atom, atListIndex: index.subIndex) + } else { + radical.radicand.insert(atom, atListIndex: index.subIndex) + } + + case .denominator, .numerator: + guard let frac = atoms[index.atomIndex] as? MTFraction, frac.type == .fraction else { + assertionFailure("No fraction found at index \(index.atomIndex)") + return + } + if index.subIndexType == .numerator { + frac.numerator.insert(atom, atListIndex: index.subIndex) + } else { + frac.denominator.insert(atom, atListIndex: index.subIndex) + } + + case .subscript: + guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + assert(current.subScript != nil, "No subscript for atom at index \(index.atomIndex)") + current.subScript?.insert(atom, atListIndex: index.subIndex) + + case .superscript: + guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + assert(current.superScript != nil, "No superscript for atom at index \(index.atomIndex)") + current.superScript?.insert(atom, atListIndex: index.subIndex) + + @unknown default: + break + } + } + + @objc(removeAtomAtListIndex:) + public func removeAtom(atListIndex index: MTMathListIndex) { + if index.atomIndex >= atoms.count { + let exception = NSException( + name: .rangeException, + reason: "Index \(index.atomIndex) is out of bounds for list of size \(atoms.count)", + userInfo: nil + ) + exception.raise() + return + } + + switch index.subIndexType { + case .none: + removeAtom(at: index.atomIndex) + + case .nucleus: + guard let currentAtom = atoms[index.atomIndex] as? MTMathAtom else { return } + assert(currentAtom.subScript != nil || currentAtom.superScript != nil, + "Nuclear fission is not supported if there are no subscripts or superscripts.") + var previous: MTMathAtom? + if index.atomIndex > 0 { + previous = atoms[index.atomIndex - 1] as? MTMathAtom + } + if let previous, + previous.subScript == nil, + previous.superScript == nil { + previous.superScript = currentAtom.superScript + previous.subScript = currentAtom.subScript + removeAtom(at: index.atomIndex) + } else { + currentAtom.nucleus = "" + } + + case .radicand, .degree: + guard let radical = atoms[index.atomIndex] as? MTRadical, radical.type == .radical else { + assertionFailure("No radical found at index \(index.atomIndex)") + return + } + if index.subIndexType == .degree { + radical.degree.removeAtom(atListIndex: index.subIndex) + } else { + radical.radicand.removeAtom(atListIndex: index.subIndex) + } + + case .denominator, .numerator: + guard let frac = atoms[index.atomIndex] as? MTFraction, frac.type == .fraction else { + assertionFailure("No fraction found at index \(index.atomIndex)") + return + } + if index.subIndexType == .numerator { + frac.numerator.removeAtom(atListIndex: index.subIndex) + } else { + frac.denominator.removeAtom(atListIndex: index.subIndex) + } + + case .subscript: + guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + assert(current.subScript != nil, "No subscript for atom at index \(index.atomIndex)") + current.subScript?.removeAtom(atListIndex: index.subIndex) + + case .superscript: + guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + assert(current.superScript != nil, "No superscript for atom at index \(index.atomIndex)") + current.superScript?.removeAtom(atListIndex: index.subIndex) + + @unknown default: + break + } + } + + @objc(removeAtomsInListIndexRange:) + public func removeAtoms(inListIndexRange range: MTMathListRange) { + let start = range.start + + switch start.subIndexType { + case .none: + removeAtoms(in: NSRange(location: start.atomIndex, length: range.length)) + + case .nucleus: + assertionFailure("Nuclear fission is not supported") + + case .radicand, .degree: + guard let radical = atoms[start.atomIndex] as? MTRadical, radical.type == .radical else { + assertionFailure("No radical found at index \(start.atomIndex)") + return + } + if start.subIndexType == .degree { + radical.degree.removeAtoms(inListIndexRange: range.subIndexRange) + } else { + radical.radicand.removeAtoms(inListIndexRange: range.subIndexRange) + } + + case .denominator, .numerator: + guard let frac = atoms[start.atomIndex] as? MTFraction, frac.type == .fraction else { + assertionFailure("No fraction found at index \(start.atomIndex)") + return + } + if start.subIndexType == .numerator { + frac.numerator.removeAtoms(inListIndexRange: range.subIndexRange) + } else { + frac.denominator.removeAtoms(inListIndexRange: range.subIndexRange) + } + + case .subscript: + guard let current = atoms[start.atomIndex] as? MTMathAtom else { return } + assert(current.subScript != nil, "No subscript for atom at index \(start.atomIndex)") + current.subScript?.removeAtoms(inListIndexRange: range.subIndexRange) + + case .superscript: + guard let current = atoms[start.atomIndex] as? MTMathAtom else { return } + assert(current.superScript != nil, "No superscript for atom at index \(start.atomIndex)") + current.superScript?.removeAtoms(inListIndexRange: range.subIndexRange) + + @unknown default: + break + } + } + + @objc(atomAtListIndex:) + public func atom(atListIndex index: MTMathListIndex?) -> MTMathAtom? { + guard let index else { return nil } + guard index.atomIndex < atoms.count else { return nil } + guard let atom = atoms[index.atomIndex] as? MTMathAtom else { return nil } + + switch index.subIndexType { + case .none, .nucleus: + return atom + + case .subscript: + return atom.subScript?.atom(atListIndex: index.subIndex) + + case .superscript: + return atom.superScript?.atom(atListIndex: index.subIndex) + + case .radicand, .degree: + guard let radical = atom as? MTRadical, atom.type == .radical else { + return nil + } + if index.subIndexType == .degree { + return radical.degree.atom(atListIndex: index.subIndex) + } else { + return radical.radicand.atom(atListIndex: index.subIndex) + } + + case .numerator, .denominator: + guard let frac = atom as? MTFraction, atom.type == .fraction else { + return nil + } + if index.subIndexType == .denominator { + return frac.denominator.atom(atListIndex: index.subIndex) + } else { + return frac.numerator.atom(atListIndex: index.subIndex) + } + + @unknown default: + return nil + } + } +} From 1527558506c37db2ae3fcfb721a51db0741b054a Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 14:30:09 +0000 Subject: [PATCH 091/133] Fix MTMathList editing Swift API compatibility --- .../Internal/MTMathList+Editing.swift | 195 +++++++++++------- 1 file changed, 119 insertions(+), 76 deletions(-) diff --git a/mathEditorSwift/Internal/MTMathList+Editing.swift b/mathEditorSwift/Internal/MTMathList+Editing.swift index 74dd70b..136c069 100644 --- a/mathEditorSwift/Internal/MTMathList+Editing.swift +++ b/mathEditorSwift/Internal/MTMathList+Editing.swift @@ -11,7 +11,7 @@ import iosMath extension MTMathList { @objc(insertAtom:atListIndex:) public func insert(_ atom: MTMathAtom, atListIndex index: MTMathListIndex) { - if index.atomIndex > atoms.count { + if index.atomIndex > UInt(atoms.count) { let exception = NSException( name: .rangeException, reason: "Index \(index.atomIndex) is out of bounds for list of size \(atoms.count)", @@ -22,53 +22,66 @@ extension MTMathList { } switch index.subIndexType { - case .none: + case .subIndexTypeNone: insert(atom, at: index.atomIndex) - case .nucleus: - guard let currentAtom = atoms[index.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeNucleus: + let atomIndex = Int(index.atomIndex) + guard let currentAtom = atoms[atomIndex] as? MTMathAtom else { return } assert(currentAtom.subScript != nil || currentAtom.superScript != nil, "Nuclear fusion is not supported if there are no subscripts or superscripts.") assert(atom.subScript == nil && atom.superScript == nil, "Cannot fuse with an atom that already has a subscript or a superscript") + guard let subIndex = index.sub else { return } atom.subScript = currentAtom.subScript atom.superScript = currentAtom.superScript currentAtom.subScript = nil currentAtom.superScript = nil - insert(atom, at: index.atomIndex + index.subIndex.atomIndex) + insert(atom, at: index.atomIndex + subIndex.atomIndex) - case .degree, .radicand: - guard let radical = atoms[index.atomIndex] as? MTRadical, radical.type == .radical else { + case .subIndexTypeDegree, .subIndexTypeRadicand: + let atomIndex = Int(index.atomIndex) + guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else { assertionFailure("No radical found at index \(index.atomIndex)") return } - if index.subIndexType == .degree { - radical.degree.insert(atom, atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + if index.subIndexType == .subIndexTypeDegree { + radical.degree.insert(atom, atListIndex: subIndex) } else { - radical.radicand.insert(atom, atListIndex: index.subIndex) + radical.radicand.insert(atom, atListIndex: subIndex) } - case .denominator, .numerator: - guard let frac = atoms[index.atomIndex] as? MTFraction, frac.type == .fraction else { + case .subIndexTypeDenominator, .subIndexTypeNumerator: + let atomIndex = Int(index.atomIndex) + guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else { assertionFailure("No fraction found at index \(index.atomIndex)") return } - if index.subIndexType == .numerator { - frac.numerator.insert(atom, atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + if index.subIndexType == .subIndexTypeNumerator { + frac.numerator.insert(atom, atListIndex: subIndex) } else { - frac.denominator.insert(atom, atListIndex: index.subIndex) + frac.denominator.insert(atom, atListIndex: subIndex) } - case .subscript: - guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeSubscript: + let atomIndex = Int(index.atomIndex) + guard let current = atoms[atomIndex] as? MTMathAtom else { return } assert(current.subScript != nil, "No subscript for atom at index \(index.atomIndex)") - current.subScript?.insert(atom, atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + current.subScript?.insert(atom, atListIndex: subIndex) - case .superscript: - guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeSuperscript: + let atomIndex = Int(index.atomIndex) + guard let current = atoms[atomIndex] as? MTMathAtom else { return } assert(current.superScript != nil, "No superscript for atom at index \(index.atomIndex)") - current.superScript?.insert(atom, atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + current.superScript?.insert(atom, atListIndex: subIndex) + + case .subIndexTypeInner: + break @unknown default: break @@ -77,7 +90,7 @@ extension MTMathList { @objc(removeAtomAtListIndex:) public func removeAtom(atListIndex index: MTMathListIndex) { - if index.atomIndex >= atoms.count { + if index.atomIndex >= UInt(atoms.count) { let exception = NSException( name: .rangeException, reason: "Index \(index.atomIndex) is out of bounds for list of size \(atoms.count)", @@ -88,16 +101,17 @@ extension MTMathList { } switch index.subIndexType { - case .none: + case .subIndexTypeNone: removeAtom(at: index.atomIndex) - case .nucleus: - guard let currentAtom = atoms[index.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeNucleus: + let atomIndex = Int(index.atomIndex) + guard let currentAtom = atoms[atomIndex] as? MTMathAtom else { return } assert(currentAtom.subScript != nil || currentAtom.superScript != nil, "Nuclear fission is not supported if there are no subscripts or superscripts.") var previous: MTMathAtom? if index.atomIndex > 0 { - previous = atoms[index.atomIndex - 1] as? MTMathAtom + previous = atoms[Int(index.atomIndex - 1)] as? MTMathAtom } if let previous, previous.subScript == nil, @@ -109,37 +123,48 @@ extension MTMathList { currentAtom.nucleus = "" } - case .radicand, .degree: - guard let radical = atoms[index.atomIndex] as? MTRadical, radical.type == .radical else { + case .subIndexTypeRadicand, .subIndexTypeDegree: + let atomIndex = Int(index.atomIndex) + guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else { assertionFailure("No radical found at index \(index.atomIndex)") return } - if index.subIndexType == .degree { - radical.degree.removeAtom(atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + if index.subIndexType == .subIndexTypeDegree { + radical.degree.removeAtom(atListIndex: subIndex) } else { - radical.radicand.removeAtom(atListIndex: index.subIndex) + radical.radicand.removeAtom(atListIndex: subIndex) } - case .denominator, .numerator: - guard let frac = atoms[index.atomIndex] as? MTFraction, frac.type == .fraction else { + case .subIndexTypeDenominator, .subIndexTypeNumerator: + let atomIndex = Int(index.atomIndex) + guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else { assertionFailure("No fraction found at index \(index.atomIndex)") return } - if index.subIndexType == .numerator { - frac.numerator.removeAtom(atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + if index.subIndexType == .subIndexTypeNumerator { + frac.numerator.removeAtom(atListIndex: subIndex) } else { - frac.denominator.removeAtom(atListIndex: index.subIndex) + frac.denominator.removeAtom(atListIndex: subIndex) } - case .subscript: - guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeSubscript: + let atomIndex = Int(index.atomIndex) + guard let current = atoms[atomIndex] as? MTMathAtom else { return } assert(current.subScript != nil, "No subscript for atom at index \(index.atomIndex)") - current.subScript?.removeAtom(atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + current.subScript?.removeAtom(atListIndex: subIndex) - case .superscript: - guard let current = atoms[index.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeSuperscript: + let atomIndex = Int(index.atomIndex) + guard let current = atoms[atomIndex] as? MTMathAtom else { return } assert(current.superScript != nil, "No superscript for atom at index \(index.atomIndex)") - current.superScript?.removeAtom(atListIndex: index.subIndex) + guard let subIndex = index.sub else { return } + current.superScript?.removeAtom(atListIndex: subIndex) + + case .subIndexTypeInner: + break @unknown default: break @@ -151,43 +176,54 @@ extension MTMathList { let start = range.start switch start.subIndexType { - case .none: - removeAtoms(in: NSRange(location: start.atomIndex, length: range.length)) + case .subIndexTypeNone: + removeAtoms(in: NSRange(location: Int(start.atomIndex), length: Int(range.length))) - case .nucleus: + case .subIndexTypeNucleus: assertionFailure("Nuclear fission is not supported") - case .radicand, .degree: - guard let radical = atoms[start.atomIndex] as? MTRadical, radical.type == .radical else { + case .subIndexTypeRadicand, .subIndexTypeDegree: + let atomIndex = Int(start.atomIndex) + guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else { assertionFailure("No radical found at index \(start.atomIndex)") return } - if start.subIndexType == .degree { - radical.degree.removeAtoms(inListIndexRange: range.subIndexRange) + guard let subIndexRange = range.subIndex() else { return } + if start.subIndexType == .subIndexTypeDegree { + radical.degree.removeAtoms(inListIndexRange: subIndexRange) } else { - radical.radicand.removeAtoms(inListIndexRange: range.subIndexRange) + radical.radicand.removeAtoms(inListIndexRange: subIndexRange) } - case .denominator, .numerator: - guard let frac = atoms[start.atomIndex] as? MTFraction, frac.type == .fraction else { + case .subIndexTypeDenominator, .subIndexTypeNumerator: + let atomIndex = Int(start.atomIndex) + guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else { assertionFailure("No fraction found at index \(start.atomIndex)") return } - if start.subIndexType == .numerator { - frac.numerator.removeAtoms(inListIndexRange: range.subIndexRange) + guard let subIndexRange = range.subIndex() else { return } + if start.subIndexType == .subIndexTypeNumerator { + frac.numerator.removeAtoms(inListIndexRange: subIndexRange) } else { - frac.denominator.removeAtoms(inListIndexRange: range.subIndexRange) + frac.denominator.removeAtoms(inListIndexRange: subIndexRange) } - case .subscript: - guard let current = atoms[start.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeSubscript: + let atomIndex = Int(start.atomIndex) + guard let current = atoms[atomIndex] as? MTMathAtom else { return } assert(current.subScript != nil, "No subscript for atom at index \(start.atomIndex)") - current.subScript?.removeAtoms(inListIndexRange: range.subIndexRange) + guard let subIndexRange = range.subIndex() else { return } + current.subScript?.removeAtoms(inListIndexRange: subIndexRange) - case .superscript: - guard let current = atoms[start.atomIndex] as? MTMathAtom else { return } + case .subIndexTypeSuperscript: + let atomIndex = Int(start.atomIndex) + guard let current = atoms[atomIndex] as? MTMathAtom else { return } assert(current.superScript != nil, "No superscript for atom at index \(start.atomIndex)") - current.superScript?.removeAtoms(inListIndexRange: range.subIndexRange) + guard let subIndexRange = range.subIndex() else { return } + current.superScript?.removeAtoms(inListIndexRange: subIndexRange) + + case .subIndexTypeInner: + break @unknown default: break @@ -197,39 +233,46 @@ extension MTMathList { @objc(atomAtListIndex:) public func atom(atListIndex index: MTMathListIndex?) -> MTMathAtom? { guard let index else { return nil } - guard index.atomIndex < atoms.count else { return nil } - guard let atom = atoms[index.atomIndex] as? MTMathAtom else { return nil } + guard index.atomIndex < UInt(atoms.count) else { return nil } + guard let atom = atoms[Int(index.atomIndex)] as? MTMathAtom else { return nil } switch index.subIndexType { - case .none, .nucleus: + case .subIndexTypeNone, .subIndexTypeNucleus: return atom - case .subscript: - return atom.subScript?.atom(atListIndex: index.subIndex) + case .subIndexTypeSubscript: + guard let subIndex = index.sub else { return nil } + return atom.subScript?.atom(atListIndex: subIndex) - case .superscript: - return atom.superScript?.atom(atListIndex: index.subIndex) + case .subIndexTypeSuperscript: + guard let subIndex = index.sub else { return nil } + return atom.superScript?.atom(atListIndex: subIndex) - case .radicand, .degree: + case .subIndexTypeRadicand, .subIndexTypeDegree: guard let radical = atom as? MTRadical, atom.type == .radical else { return nil } - if index.subIndexType == .degree { - return radical.degree.atom(atListIndex: index.subIndex) + guard let subIndex = index.sub else { return nil } + if index.subIndexType == .subIndexTypeDegree { + return radical.degree.atom(atListIndex: subIndex) } else { - return radical.radicand.atom(atListIndex: index.subIndex) + return radical.radicand.atom(atListIndex: subIndex) } - case .numerator, .denominator: + case .subIndexTypeNumerator, .subIndexTypeDenominator: guard let frac = atom as? MTFraction, atom.type == .fraction else { return nil } - if index.subIndexType == .denominator { - return frac.denominator.atom(atListIndex: index.subIndex) + guard let subIndex = index.sub else { return nil } + if index.subIndexType == .subIndexTypeDenominator { + return frac.denominator.atom(atListIndex: subIndex) } else { - return frac.numerator.atom(atListIndex: index.subIndex) + return frac.numerator.atom(atListIndex: subIndex) } + case .subIndexTypeInner: + return nil + @unknown default: return nil } From f5a77d873024adb718bf71676737e96105cb65f1 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 14:40:44 +0000 Subject: [PATCH 092/133] Migrate MTDisplay editing categories to Swift --- mathEditor/internal/MTDisplay+Editing.m | 596 ------------------ .../Internal/MTDisplay+Editing.swift | 419 ++++++++++++ 2 files changed, 419 insertions(+), 596 deletions(-) delete mode 100644 mathEditor/internal/MTDisplay+Editing.m create mode 100644 mathEditorSwift/Internal/MTDisplay+Editing.swift diff --git a/mathEditor/internal/MTDisplay+Editing.m b/mathEditor/internal/MTDisplay+Editing.m deleted file mode 100644 index a60ef5b..0000000 --- a/mathEditor/internal/MTDisplay+Editing.m +++ /dev/null @@ -1,596 +0,0 @@ -// -// MTDisplay+Editing.m -// -// Created by Kostub Deshmukh on 9/6/13. -// Copyright (C) 2013 MathChat -// -// This software may be modified and distributed under the terms of the -// MIT license. See the LICENSE file for details. -// - -#import - -#import "MTDisplay+Editing.h" -#import "MTMathList+Editing.h" - -static CGPoint kInvalidPosition = { -1, -1}; -// Number of pixels outside the bound to allow a point to be considered as part of the bounds. -static CGFloat kPixelDelta = 2; - -#pragma mark Unicode functions - -NSUInteger numCodePointsInRange(NSString* str, NSRange range) { - if (range.length > 0) { - // doesn't work correctly if range is 0 - NSRange grown = [str rangeOfComposedCharacterSequencesForRange:range]; - - unichar buffer[grown.length]; - [str getCharacters:buffer range:grown]; - int count = 0; - for (int i = 0; i < grown.length; i++) { - count++; - // we check both high and low due to work for both endianess - if (CFStringIsSurrogateHighCharacter(buffer[i]) || CFStringIsSurrogateLowCharacter(buffer[i])) { - // skip the next character - i++; - } - } - return count; - } - return 0; -} - -static NSUInteger codePointIndexToStringIndex(NSString* str, const NSUInteger codePointIndex) { - unichar buffer[str.length]; - [str getCharacters:buffer range:NSMakeRange(0, str.length)]; - int codePointCount = 0; - for (int i = 0; i < str.length; i++, codePointCount++) { - if (codePointCount == codePointIndex) { - return i; // this is the string index - } - // we check both high and low due to work for both endianess - if (CFStringIsSurrogateHighCharacter(buffer[i]) || CFStringIsSurrogateLowCharacter(buffer[i])) { - // skip the next character - i++; - } - } - - // the index is out of range - return NSNotFound; -} - -#pragma mark - Distance utilities -// Calculates the manhattan distance from a point to the nearest boundary of the rectangle -static CGFloat distanceFromPointToRect(CGPoint point, CGRect rect) { - CGFloat distance = 0; - if (point.x < rect.origin.x) { - distance += (rect.origin.x - point.x); - } else if (point.x > CGRectGetMaxX(rect)) { - distance += point.x - CGRectGetMaxX(rect); - } - - if (point.y < rect.origin.y) { - distance += (rect.origin.y - point.y); - } else if (point.y > CGRectGetMaxY(rect)) { - distance += point.y - CGRectGetMaxY(rect); - } - return distance; -} - -# pragma mark - MTDisplay - -@implementation MTDisplay (Editing) - -// Empty implementations for the base class - -- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point -{ - return nil; -} - -- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index -{ - return kInvalidPosition; -} - -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color -{ -} - -- (void)highlightWithColor:(MTColor *)color -{ -} -@end - -# pragma mark - MTCTLineDisplay - -@interface MTCTLineDisplay (Editing) - -// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. -- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - -// The bounds of the display indicated by the given index -- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; - -// Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; - -- (void)highlightWithColor:(MTColor *)color; - -@end - -@implementation MTCTLineDisplay (Editing) - -- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point -{ - // Convert the point to the reference of the CTLine - CGPoint relativePoint = CGPointMake(point.x - self.position.x, point.y - self.position.y); - CFIndex index = CTLineGetStringIndexForPosition(self.line, relativePoint); - if (index == kCFNotFound) { - return nil; - } - // The index returned is in UTF-16, translate to codepoint index. - // NSUInteger codePointIndex = stringIndexToCodePointIndex(self.attributedString.string, index); - // Convert the code point index to an index into the mathlist - NSUInteger mlIndex = [self convertToMathListIndex:index]; - // index will be between 0 and _range.length inclusive - NSAssert(mlIndex >= 0 && mlIndex <= self.range.length, @"Returned index out of range: %ld, range (%@, %@)", index, @(self.range.location), @(self.range.length)); - // translate to the current index - MTMathListIndex* listIndex = [MTMathListIndex level0Index:self.range.location + mlIndex]; - return listIndex; -} - -- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index -{ - CGFloat offset; - NSAssert(index.subIndexType == kMTSubIndexTypeNone, @"Index in a CTLineDisplay cannot have sub indexes."); - if (index.atomIndex == NSMaxRange(self.range)) { - offset = self.width; - } else { - NSAssert(NSLocationInRange(index.atomIndex, self.range), @"Index %@ not in range %@", index, NSStringFromRange(self.range)); - NSUInteger strIndex = [self mathListIndexToStringIndex:index.atomIndex - self.range.location]; - //CFIndex charIndex = codePointIndexToStringIndex(self.attributedString.string, strIndex); - offset = CTLineGetOffsetForStringIndex(self.line, strIndex, NULL); - } - return CGPointMake(self.position.x + offset, self.position.y); -} - - -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color -{ - assert(NSLocationInRange(index.atomIndex, self.range)); - assert(index.subIndexType == kMTSubIndexTypeNone || index.subIndexType == kMTSubIndexTypeNucleus); - if (index.subIndexType == kMTSubIndexTypeNucleus) { - NSAssert(false, @"Nucleus highlighting not supported yet"); - } - // index is in unicode code points, while attrString is not - CFIndex charIndex = codePointIndexToStringIndex(self.attributedString.string, index.atomIndex - self.range.location); - assert(charIndex != NSNotFound); - - NSMutableAttributedString* attrStr = self.attributedString.mutableCopy; - [attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[color CGColor] - range:[attrStr.string rangeOfComposedCharacterSequenceAtIndex:charIndex]]; - self.attributedString = attrStr; -} - -- (void)highlightWithColor:(MTColor *)color -{ - NSMutableAttributedString* attrStr = self.attributedString.mutableCopy; - [attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[color CGColor] - range:NSMakeRange(0, attrStr.length)]; - self.attributedString = attrStr; -} - -// Convert the index into the current string to an index into the mathlist. These may not be the same since a single -// math atom may have multiple characters. -- (NSUInteger) convertToMathListIndex:(NSUInteger) strIndex -{ - NSUInteger strLenCovered = 0; - for (NSUInteger mlIndex = 0; mlIndex < self.atoms.count; mlIndex++) { - if (strLenCovered >= strIndex) { - return mlIndex; - } - MTMathAtom* atom = self.atoms[mlIndex]; - strLenCovered += atom.nucleus.length; - } - // By the time we come to the end of the string, we should have covered all the characters. - NSAssert(strLenCovered >= strIndex, @"StrIndex should not be more than the len covered"); - return self.atoms.count; -} - -- (NSUInteger) mathListIndexToStringIndex:(NSUInteger) mlIndex -{ - NSAssert(mlIndex < self.atoms.count, @"Index %@ not in range %@", @(mlIndex), @(self.atoms.count)); - NSUInteger strIndex = 0; - for (NSUInteger i = 0; i < mlIndex; i++) { - MTMathAtom* atom = self.atoms[i]; - strIndex += atom.nucleus.length; - } - return strIndex; -} - -@end - -#pragma mark - MTFractionDisplay - -@interface MTFractionDisplay (Editing) - -// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. -- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - -// The bounds of the display indicated by the given index -- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; - -// Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; - -- (void)highlightWithColor:(MTColor *)color; - -- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type; - -@end - -@implementation MTFractionDisplay (Editing) - -- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point -{ - // We can be before the fraction or after the fraction - if (point.x < self.position.x - kPixelDelta) { - // we are before the fraction, so - return [MTMathListIndex level0Index:self.range.location]; - } else if (point.x > self.position.x + self.width + kPixelDelta) { - // we are after the fraction - return [MTMathListIndex level0Index:NSMaxRange(self.range)]; - } else { - // we can be either near the numerator or the denominator - CGFloat numeratorDistance = distanceFromPointToRect(point, self.numerator.displayBounds); - CGFloat denominatorDistance = distanceFromPointToRect(point, self.denominator.displayBounds); - if (numeratorDistance < denominatorDistance) { - return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.numerator closestIndexToPoint:point] type:kMTSubIndexTypeNumerator]; - } else { - return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.denominator closestIndexToPoint:point] type:kMTSubIndexTypeDenominator]; - } - } -} - -// Seems never used -- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index -{ - assert(index.subIndexType == kMTSubIndexTypeNone); - // draw a caret before the fraction - return CGPointMake(self.position.x, self.position.y); -} - -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color -{ - assert(index.subIndexType == kMTSubIndexTypeNone); - [self highlightWithColor:color]; -} - -- (void)highlightWithColor:(MTColor *)color -{ - [self.numerator highlightWithColor:color]; - [self.denominator highlightWithColor:color]; -} - -- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type -{ - switch (type) { - case kMTSubIndexTypeNumerator: - return self.numerator; - - case kMTSubIndexTypeDenominator: - return self.denominator; - - case kMTSubIndexTypeDegree: - case kMTSubIndexTypeRadicand: - case kMTSubIndexTypeNucleus: - case kMTSubIndexTypeSubscript: - case kMTSubIndexTypeSuperscript: - case kMTSubIndexTypeNone: - NSAssert(false, @"Not a fraction subtype %d", type); - return nil; - } -} - -@end - -#pragma mark - MTRadicalDisplay - -@interface MTRadicalDisplay (Editing) - -// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. -- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - -// The bounds of the display indicated by the given index -- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; - -// Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; - -- (void)highlightWithColor:(MTColor *)color; - -- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type; - -@end - -@implementation MTRadicalDisplay (Editing) - - -- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point -{ - // We can be before the radical or after the radical - if (point.x < self.position.x - kPixelDelta) { - // we are before the radical, so - return [MTMathListIndex level0Index:self.range.location]; - } else if (point.x > self.position.x + self.width + kPixelDelta) { - // we are after the radical - return [MTMathListIndex level0Index:NSMaxRange(self.range)]; - } else { - // we are in the radical - CGFloat degreeDistance = distanceFromPointToRect(point, self.degree.displayBounds); - CGFloat radicandDistance = distanceFromPointToRect(point, self.radicand.displayBounds); - - if (degreeDistance < radicandDistance) { - return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.degree closestIndexToPoint:point] type:kMTSubIndexTypeDegree]; - } else { - return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.radicand closestIndexToPoint:point] type:kMTSubIndexTypeRadicand]; - } - - } -} - -// TODO seems unused -- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index -{ - assert(index.subIndexType == kMTSubIndexTypeNone); - // draw a caret - return CGPointMake(self.position.x, self.position.y); -} - -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color -{ - assert(index.subIndexType == kMTSubIndexTypeNone); - [self highlightWithColor:color]; -} - -- (void)highlightWithColor:(MTColor *)color -{ - [self.radicand highlightWithColor:color]; -} - -- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type -{ - switch (type) { - - case kMTSubIndexTypeRadicand: - return self.radicand; - case kMTSubIndexTypeDegree: - return self.degree; - case kMTSubIndexTypeNumerator: - - case kMTSubIndexTypeDenominator: - - case kMTSubIndexTypeNucleus: - case kMTSubIndexTypeSubscript: - case kMTSubIndexTypeSuperscript: - case kMTSubIndexTypeNone: - NSAssert(false, @"Not a radical subtype %d", type); - return nil; - } -} - -@end - -#pragma mark - MTMathListDisplay - -@interface MTMathListDisplay (Editing) - -// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. -- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - -// The bounds of the display indicated by the given index -- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; - -// Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; - -- (void)highlightWithColor:(MTColor *)color; - -@end - -@implementation MTMathListDisplay (Editing) - - -- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point -{ - // the origin of for the subelements of a MTMathList is the current position, so translate the current point to our origin. - CGPoint translatedPoint = CGPointMake(point.x - self.position.x, point.y - self.position.y); - - MTDisplay* closest = nil; - NSMutableArray* xbounds = [NSMutableArray array]; - CGFloat minDistance = CGFLOAT_MAX; - for (MTDisplay* atom in self.subDisplays) { - CGRect bounds = atom.displayBounds; - CGFloat maxBoundsX = CGRectGetMaxX(bounds); - - if (bounds.origin.x - kPixelDelta <= translatedPoint.x && translatedPoint.x <= maxBoundsX + kPixelDelta) { - [xbounds addObject:atom]; - } - - CGFloat distance = distanceFromPointToRect(translatedPoint, bounds); - if (distance < minDistance) { - closest = atom; - minDistance = distance; - } - } - MTDisplay* atomWithPoint = nil; - if (xbounds.count == 0) { - if (translatedPoint.x <= -kPixelDelta) { - // all the way to the left - return [MTMathListIndex level0Index:self.range.location]; - } else if (translatedPoint.x >= self.width + kPixelDelta) { - // all the way to the right - return [MTMathListIndex level0Index:NSMaxRange(self.range)]; - } else { - // It is within the mathlist but not within the x bounds of any sublist. Use the closest in that case. - atomWithPoint = closest; - } - } else if (xbounds.count == 1) { - atomWithPoint = xbounds[0]; - if (translatedPoint.x >= self.width - kPixelDelta) { - // The point is close to the end. Only use the selected x bounds if the y is within range. - if (translatedPoint.y <= CGRectGetMinY(atomWithPoint.displayBounds) - kPixelDelta) { - // The point is less than the y including the delta. Move the cursor to the end rather than in this atom. - return [MTMathListIndex level0Index:NSMaxRange(self.range)]; - } - } - } else { - // Use the closest since there are more than 2 sublists which have this x position. - atomWithPoint = closest; - } - - if (!atomWithPoint) { - return nil; - } - - MTMathListIndex* index = [atomWithPoint closestIndexToPoint:(CGPoint)translatedPoint]; - if ([atomWithPoint isKindOfClass:[MTMathListDisplay class]]) { - MTMathListDisplay* closestLine = (MTMathListDisplay*) atomWithPoint; - NSAssert(closestLine.type == kMTLinePositionSubscript || closestLine.type == kMTLinePositionSuperscript, @"MTLine type regular inside an MTLine - shouldn't happen"); - // This is a subscript or a superscript, return the right type of subindex - MTMathListSubIndexType type = (closestLine.type == kMTLinePositionSubscript) ? kMTSubIndexTypeSubscript : kMTSubIndexTypeSuperscript; - // The index of the atom this denotes. - NSAssert(closestLine.index != NSNotFound, @"Index is not set for a subscript/superscript"); - return [MTMathListIndex indexAtLocation:closestLine.index withSubIndex:index type:type]; - } else if (atomWithPoint.hasScript) { - // The display list has a subscript or a superscript. If the index is at the end of the atom, then we need to put it before the sub/super script rather than after. - if (index.atomIndex == NSMaxRange(atomWithPoint.range)) { - return [MTMathListIndex indexAtLocation:index.atomIndex - 1 withSubIndex:[MTMathListIndex level0Index:1] type:kMTSubIndexTypeNucleus]; - } - } - return index; -} - -- (MTDisplay*) subAtomForIndex:(MTMathListIndex*) index -{ - // Inside the range - if (index.subIndexType == kMTSubIndexTypeSuperscript || index.subIndexType == kMTSubIndexTypeSubscript) { - for (MTDisplay* atom in self.subDisplays) { - if ([atom isKindOfClass:[MTMathListDisplay class]]) { - MTMathListDisplay* lineAtom = (MTMathListDisplay*) atom; - if (index.atomIndex == lineAtom.index) { - // this is the right character for the sub/superscript - // Check that it's type matches the index - if ((lineAtom.type == kMTLinePositionSubscript && index.subIndexType == kMTSubIndexTypeSubscript) - || (lineAtom.type == kMTLinePositionSuperscript && index.subIndexType == kMTSubIndexTypeSuperscript)) { - return lineAtom; - } - } - } - } - } else { - for (MTDisplay* atom in self.subDisplays) { - if (![atom isKindOfClass:[MTMathListDisplay class]] && NSLocationInRange(index.atomIndex, atom.range)) { - // not a subscript/superscript and - // jackpot, the index is in the range of this atom. - switch (index.subIndexType) { - case kMTSubIndexTypeNone: - case kMTSubIndexTypeNucleus: - return atom; - - case kMTSubIndexTypeDegree: - case kMTSubIndexTypeRadicand: - if ([atom isKindOfClass:[MTRadicalDisplay class]]) { - MTRadicalDisplay *radical = (MTRadicalDisplay *) atom; - return [radical subAtomForIndexType:index.subIndexType]; - } else { - NSLog(@"No radical at index: %lu", (unsigned long)index.atomIndex); - return nil; - } - - case kMTSubIndexTypeNumerator: - case kMTSubIndexTypeDenominator: - if ([atom isKindOfClass:[MTFractionDisplay class]]) { - MTFractionDisplay* frac = (MTFractionDisplay*) atom; - return [frac subAtomForIndexType:index.subIndexType]; - } else { - NSLog(@"No fraction at index: %lu", (unsigned long)index.atomIndex); - return nil; - } - - case kMTSubIndexTypeSubscript: - case kMTSubIndexTypeSuperscript: - assert(false); // Can't happen - break; - } - // We found the right subatom - break; - } - } - } - return nil; -} - -- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index -{ - CGPoint position = kInvalidPosition; - if (!index) { - return kInvalidPosition; - } - - if (index.atomIndex == NSMaxRange(self.range)) { - // Special case the edge of the range - position = CGPointMake(self.width, 0); - } else if (NSLocationInRange(index.atomIndex, self.range)) { - MTDisplay* atom = [self subAtomForIndex:index]; - if (index.subIndexType == kMTSubIndexTypeNucleus) { - NSUInteger nucleusPosition = index.atomIndex + index.subIndex.atomIndex; - position = [atom caretPositionForIndex:[MTMathListIndex level0Index:nucleusPosition]]; - } else if (index.subIndexType == kMTSubIndexTypeNone) { - position = [atom caretPositionForIndex:index]; - } else { - // recurse - position = [atom caretPositionForIndex:index.subIndex]; - } - } else { - // outside the range - return kInvalidPosition; - } - - if (CGPointEqualToPoint(position, kInvalidPosition)) { - // we didn't find the position - return position; - } - - // convert bounds from our coordinate system before returning - position.x += self.position.x; - position.y += self.position.y; - return position; -} - - -- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color -{ - if (!index) { - return; - } - if (NSLocationInRange(index.atomIndex, self.range)) { - MTDisplay* atom = [self subAtomForIndex:index]; - if (index.subIndexType == kMTSubIndexTypeNucleus || index.subIndexType == kMTSubIndexTypeNone) { - [atom highlightCharacterAtIndex:index color:color]; - } else { - // recurse - [atom highlightCharacterAtIndex:index.subIndex color:color]; - } - } -} - -- (void)highlightWithColor:(MTColor *)color -{ - for (MTDisplay* atom in self.subDisplays) { - [atom highlightWithColor:color]; - } -} - -@end diff --git a/mathEditorSwift/Internal/MTDisplay+Editing.swift b/mathEditorSwift/Internal/MTDisplay+Editing.swift new file mode 100644 index 0000000..f97e840 --- /dev/null +++ b/mathEditorSwift/Internal/MTDisplay+Editing.swift @@ -0,0 +1,419 @@ +// +// MTDisplay+Editing.swift +// MathEditor +// +// Created by Madiyar Aitbayev on 24/03/2026. +// + +import CoreText +import Foundation +import iosMath + +private let invalidPosition = CGPoint(x: -1, y: -1) +private let pixelDelta: CGFloat = 2 + +private func codePointIndexToStringIndex(_ str: String, _ codePointIndex: UInt) -> Int { + let utf16 = Array(str.utf16) + var codePointCount: UInt = 0 + var i = 0 + + while i < utf16.count { + if codePointCount == codePointIndex { + return i + } + codePointCount += 1 + + let c = utf16[i] + if CFStringIsSurrogateHighCharacter(c) || CFStringIsSurrogateLowCharacter(c) { + i += 1 + } + i += 1 + } + + return NSNotFound +} + +private func distanceFromPointToRect(_ point: CGPoint, _ rect: CGRect) -> CGFloat { + var distance: CGFloat = 0 + + if point.x < rect.origin.x { + distance += rect.origin.x - point.x + } else if point.x > rect.maxX { + distance += point.x - rect.maxX + } + + if point.y < rect.origin.y { + distance += rect.origin.y - point.y + } else if point.y > rect.maxY { + distance += point.y - rect.maxY + } + + return distance +} + +extension MTDisplay { + @objc(closestIndexToPoint:) + public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + nil + } + + @objc(caretPositionForIndex:) + public func caretPosition(for index: MTMathListIndex) -> CGPoint { + invalidPosition + } + + @objc(highlightCharacterAtIndex:color:) + public func highlightCharacter(at index: MTMathListIndex, color: MTColor) {} + + @objc(highlightWithColor:) + public func highlight(with color: MTColor) {} +} + +extension MTCTLineDisplay { + @objc(closestIndexToPoint:) + public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + let relativePoint = CGPoint(x: point.x - position.x, y: point.y - position.y) + let idx = CTLineGetStringIndexForPosition(line, relativePoint) + if idx == kCFNotFound { + return nil + } + + let mlIndex = convertToMathListIndex(UInt(idx)) + assert(mlIndex <= UInt(range.length), "Returned index out of range: \(idx)") + return MTMathListIndex.level0Index(UInt(range.location) + mlIndex) + } + + @objc(caretPositionForIndex:) + public func caretPosition(for index: MTMathListIndex) -> CGPoint { + assert(index.subIndexType == .subIndexTypeNone, "Index in a CTLineDisplay cannot have sub indexes.") + + let offset: CGFloat + if Int(index.atomIndex) == NSMaxRange(range) { + offset = width + } else { + assert(NSLocationInRange(Int(index.atomIndex), range), "Index not in range") + let strIndex = mathListIndexToStringIndex(index.atomIndex - UInt(range.location)) + offset = CTLineGetOffsetForStringIndex(line, strIndex, nil) + } + + return CGPoint(x: position.x + offset, y: position.y) + } + + @objc(highlightCharacterAtIndex:color:) + public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + assert(NSLocationInRange(Int(index.atomIndex), range)) + assert(index.subIndexType == .subIndexTypeNone || index.subIndexType == .subIndexTypeNucleus) + + if index.subIndexType == .subIndexTypeNucleus { + assertionFailure("Nucleus highlighting not supported yet") + } + + let charIndex = codePointIndexToStringIndex(attributedString.string, index.atomIndex - UInt(range.location)) + assert(charIndex != NSNotFound) + + let attrStr = NSMutableAttributedString(attributedString: attributedString) + let seqRange = (attrStr.string as NSString).rangeOfComposedCharacterSequence(at: charIndex) + attrStr.addAttribute(kCTForegroundColorAttributeName as NSAttributedString.Key, + value: color.cgColor, + range: seqRange) + attributedString = attrStr + } + + @objc(highlightWithColor:) + public func highlight(with color: MTColor) { + let attrStr = NSMutableAttributedString(attributedString: attributedString) + attrStr.addAttribute(kCTForegroundColorAttributeName as NSAttributedString.Key, + value: color.cgColor, + range: NSRange(location: 0, length: attrStr.length)) + attributedString = attrStr + } + + @objc(convertToMathListIndex:) + public func convertToMathListIndex(_ strIndex: UInt) -> UInt { + var strLenCovered: UInt = 0 + for mlIndex in 0..= strIndex { + return UInt(mlIndex) + } + guard let atom = atoms[mlIndex] as? MTMathAtom else { continue } + strLenCovered += UInt(atom.nucleus.count) + } + assert(strLenCovered >= strIndex, "StrIndex should not be more than the len covered") + return UInt(atoms.count) + } + + @objc(mathListIndexToStringIndex:) + public func mathListIndexToStringIndex(_ mlIndex: UInt) -> Int { + assert(mlIndex < UInt(atoms.count), "Index not in range") + + var strIndex = 0 + for i in 0.. MTMathListIndex? { + if point.x < position.x - pixelDelta { + return MTMathListIndex.level0Index(UInt(range.location)) + } else if point.x > position.x + width + pixelDelta { + return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) + } else { + let numeratorDistance = distanceFromPointToRect(point, numerator.displayBounds) + let denominatorDistance = distanceFromPointToRect(point, denominator.displayBounds) + if numeratorDistance < denominatorDistance { + return MTMathListIndex(atLocation: UInt(range.location), + withSubIndex: numerator.closestIndex(to: point), + type: .subIndexTypeNumerator) + } else { + return MTMathListIndex(atLocation: UInt(range.location), + withSubIndex: denominator.closestIndex(to: point), + type: .subIndexTypeDenominator) + } + } + } + + @objc(caretPositionForIndex:) + public func caretPosition(for index: MTMathListIndex) -> CGPoint { + assert(index.subIndexType == .subIndexTypeNone) + return CGPoint(x: position.x, y: position.y) + } + + @objc(highlightCharacterAtIndex:color:) + public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + assert(index.subIndexType == .subIndexTypeNone) + highlight(with: color) + } + + @objc(highlightWithColor:) + public func highlight(with color: MTColor) { + numerator.highlight(with: color) + denominator.highlight(with: color) + } + + @objc(subAtomForIndexType:) + public func subAtom(forIndexType type: MTMathListSubIndexType) -> MTMathListDisplay? { + switch type { + case .subIndexTypeNumerator: + return numerator + case .subIndexTypeDenominator: + return denominator + default: + assertionFailure("Not a fraction subtype \(type.rawValue)") + return nil + } + } +} + +extension MTRadicalDisplay { + @objc(closestIndexToPoint:) + public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + if point.x < position.x - pixelDelta { + return MTMathListIndex.level0Index(UInt(range.location)) + } else if point.x > position.x + width + pixelDelta { + return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) + } else { + let degreeDistance = distanceFromPointToRect(point, degree.displayBounds) + let radicandDistance = distanceFromPointToRect(point, radicand.displayBounds) + + if degreeDistance < radicandDistance { + return MTMathListIndex(atLocation: UInt(range.location), + withSubIndex: degree.closestIndex(to: point), + type: .subIndexTypeDegree) + } else { + return MTMathListIndex(atLocation: UInt(range.location), + withSubIndex: radicand.closestIndex(to: point), + type: .subIndexTypeRadicand) + } + } + } + + @objc(caretPositionForIndex:) + public func caretPosition(for index: MTMathListIndex) -> CGPoint { + assert(index.subIndexType == .subIndexTypeNone) + return CGPoint(x: position.x, y: position.y) + } + + @objc(highlightCharacterAtIndex:color:) + public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + assert(index.subIndexType == .subIndexTypeNone) + highlight(with: color) + } + + @objc(highlightWithColor:) + public func highlight(with color: MTColor) { + radicand.highlight(with: color) + } + + @objc(subAtomForIndexType:) + public func subAtom(forIndexType type: MTMathListSubIndexType) -> MTMathListDisplay? { + switch type { + case .subIndexTypeRadicand: + return radicand + case .subIndexTypeDegree: + return degree + default: + assertionFailure("Not a radical subtype \(type.rawValue)") + return nil + } + } +} + +extension MTMathListDisplay { + @objc(closestIndexToPoint:) + public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + let translatedPoint = CGPoint(x: point.x - position.x, y: point.y - position.y) + + var closest: MTDisplay? + var xbounds = [MTDisplay]() + var minDistance = CGFloat.greatestFiniteMagnitude + + for atom in subDisplays { + let bounds = atom.displayBounds + if bounds.origin.x - pixelDelta <= translatedPoint.x && translatedPoint.x <= bounds.maxX + pixelDelta { + xbounds.append(atom) + } + + let distance = distanceFromPointToRect(translatedPoint, bounds) + if distance < minDistance { + closest = atom + minDistance = distance + } + } + + let atomWithPoint: MTDisplay? + if xbounds.isEmpty { + if translatedPoint.x <= -pixelDelta { + return MTMathListIndex.level0Index(UInt(range.location)) + } else if translatedPoint.x >= width + pixelDelta { + return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) + } else { + atomWithPoint = closest + } + } else if xbounds.count == 1 { + atomWithPoint = xbounds[0] + if translatedPoint.x >= width - pixelDelta, + translatedPoint.y <= atomWithPoint!.displayBounds.minY - pixelDelta { + return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) + } + } else { + atomWithPoint = closest + } + + guard let atomWithPoint else { return nil } + + let index = atomWithPoint.closestIndex(to: translatedPoint) + + if let closestLine = atomWithPoint as? MTMathListDisplay { + assert(closestLine.type == .linePositionSubscript || closestLine.type == .linePositionSuperscript, + "MTLine type regular inside an MTLine - shouldn't happen") + let type: MTMathListSubIndexType = (closestLine.type == .linePositionSubscript) ? .subIndexTypeSubscript : .subIndexTypeSuperscript + let lineIndex = Int(closestLine.index) + guard lineIndex != NSNotFound else { return nil } + return MTMathListIndex(atLocation: UInt(lineIndex), withSubIndex: index, type: type) + } else if atomWithPoint.hasScript, let index { + if Int(index.atomIndex) == NSMaxRange(atomWithPoint.range) { + return MTMathListIndex(atLocation: index.atomIndex - 1, + withSubIndex: MTMathListIndex.level0Index(1), + type: .subIndexTypeNucleus) + } + } + + return index + } + + @objc(subAtomForIndex:) + public func subAtom(for index: MTMathListIndex) -> MTDisplay? { + if index.subIndexType == .subIndexTypeSuperscript || index.subIndexType == .subIndexTypeSubscript { + for atom in subDisplays { + if let lineAtom = atom as? MTMathListDisplay, + Int(index.atomIndex) == Int(lineAtom.index) { + if (lineAtom.type == .linePositionSubscript && index.subIndexType == .subIndexTypeSubscript) + || (lineAtom.type == .linePositionSuperscript && index.subIndexType == .subIndexTypeSuperscript) { + return lineAtom + } + } + } + } else { + for atom in subDisplays { + if !(atom is MTMathListDisplay) && NSLocationInRange(Int(index.atomIndex), atom.range) { + switch index.subIndexType { + case .subIndexTypeNone, .subIndexTypeNucleus: + return atom + + case .subIndexTypeDegree, .subIndexTypeRadicand: + if let radical = atom as? MTRadicalDisplay { + return radical.subAtom(forIndexType: index.subIndexType) + } + return nil + + case .subIndexTypeNumerator, .subIndexTypeDenominator: + if let frac = atom as? MTFractionDisplay { + return frac.subAtom(forIndexType: index.subIndexType) + } + return nil + + case .subIndexTypeSubscript, .subIndexTypeSuperscript, .subIndexTypeInner: + assertionFailure("Unexpected index type for this path") + return nil + + @unknown default: + return nil + } + } + } + } + return nil + } + + @objc(caretPositionForIndex:) + public func caretPosition(for index: MTMathListIndex) -> CGPoint { + var pos = invalidPosition + + if Int(index.atomIndex) == NSMaxRange(range) { + pos = CGPoint(x: width, y: 0) + } else if NSLocationInRange(Int(index.atomIndex), range) { + guard let atom = subAtom(for: index) else { return invalidPosition } + if index.subIndexType == .subIndexTypeNucleus { + guard let subIndex = index.sub else { return invalidPosition } + let nucleusPosition = index.atomIndex + subIndex.atomIndex + pos = atom.caretPosition(for: MTMathListIndex.level0Index(nucleusPosition)) + } else if index.subIndexType == .subIndexTypeNone { + pos = atom.caretPosition(for: index) + } else { + guard let subIndex = index.sub else { return invalidPosition } + pos = atom.caretPosition(for: subIndex) + } + } else { + return invalidPosition + } + + if pos == invalidPosition { + return pos + } + + return CGPoint(x: pos.x + position.x, y: pos.y + position.y) + } + + @objc(highlightCharacterAtIndex:color:) + public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + if NSLocationInRange(Int(index.atomIndex), range), let atom = subAtom(for: index) { + if index.subIndexType == .subIndexTypeNucleus || index.subIndexType == .subIndexTypeNone { + atom.highlightCharacter(at: index, color: color) + } else if let subIndex = index.sub { + atom.highlightCharacter(at: subIndex, color: color) + } + } + } + + @objc(highlightWithColor:) + public func highlight(with color: MTColor) { + for atom in subDisplays { + atom.highlight(with: color) + } + } +} From 89fc3d5df835bad2f37fea87af1eee7cbd61f91f Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 14:49:54 +0000 Subject: [PATCH 093/133] Fix buiild issues --- mathEditor/editor/MTEditableMathLabel.m | 1 - mathEditor/internal/MTDisplay+Editing.h | 29 ---------- .../Internal/MTDisplay+Editing.swift | 54 +++++++++---------- .../Internal/MTMathList+Editing.swift | 20 +++---- 4 files changed, 37 insertions(+), 67 deletions(-) delete mode 100644 mathEditor/internal/MTDisplay+Editing.h diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index fe6bfde..dd4b675 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -17,7 +17,6 @@ #import "MTCaretView.h" #import "MTTapGestureRecognizer.h" #import "MTMathList+Editing.h" -#import "MTDisplay+Editing.h" #import "MTUnicode.h" #import "MTMathListBuilder.h" diff --git a/mathEditor/internal/MTDisplay+Editing.h b/mathEditor/internal/MTDisplay+Editing.h deleted file mode 100644 index 7af2631..0000000 --- a/mathEditor/internal/MTDisplay+Editing.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// MTDisplay+Editing.h -// -// Created by Kostub Deshmukh on 9/6/13. -// Copyright (C) 2013 MathChat -// -// This software may be modified and distributed under the terms of the -// MIT license. See the LICENSE file for details. -// - -#import "MTMathListDisplay.h" -#import "MTMathListIndex.h" - -@interface MTDisplay (Editing) - -// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. -- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; - -// The bounds of the display indicated by the given index -- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; - -// Highlight the character(s) at the given index. -- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; - -// Highlight the entire display with the given color -- (void) highlightWithColor:(MTColor*) color; - -@end - diff --git a/mathEditorSwift/Internal/MTDisplay+Editing.swift b/mathEditorSwift/Internal/MTDisplay+Editing.swift index f97e840..e611284 100644 --- a/mathEditorSwift/Internal/MTDisplay+Editing.swift +++ b/mathEditorSwift/Internal/MTDisplay+Editing.swift @@ -71,7 +71,7 @@ extension MTDisplay { extension MTCTLineDisplay { @objc(closestIndexToPoint:) - public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { let relativePoint = CGPoint(x: point.x - position.x, y: point.y - position.y) let idx = CTLineGetStringIndexForPosition(line, relativePoint) if idx == kCFNotFound { @@ -84,7 +84,7 @@ extension MTCTLineDisplay { } @objc(caretPositionForIndex:) - public func caretPosition(for index: MTMathListIndex) -> CGPoint { + public override func caretPosition(for index: MTMathListIndex) -> CGPoint { assert(index.subIndexType == .subIndexTypeNone, "Index in a CTLineDisplay cannot have sub indexes.") let offset: CGFloat @@ -100,7 +100,7 @@ extension MTCTLineDisplay { } @objc(highlightCharacterAtIndex:color:) - public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) { assert(NSLocationInRange(Int(index.atomIndex), range)) assert(index.subIndexType == .subIndexTypeNone || index.subIndexType == .subIndexTypeNucleus) @@ -120,7 +120,7 @@ extension MTCTLineDisplay { } @objc(highlightWithColor:) - public func highlight(with color: MTColor) { + public override func highlight(with color: MTColor) { let attrStr = NSMutableAttributedString(attributedString: attributedString) attrStr.addAttribute(kCTForegroundColorAttributeName as NSAttributedString.Key, value: color.cgColor, @@ -157,14 +157,14 @@ extension MTCTLineDisplay { extension MTFractionDisplay { @objc(closestIndexToPoint:) - public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { if point.x < position.x - pixelDelta { return MTMathListIndex.level0Index(UInt(range.location)) } else if point.x > position.x + width + pixelDelta { return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) } else { - let numeratorDistance = distanceFromPointToRect(point, numerator.displayBounds) - let denominatorDistance = distanceFromPointToRect(point, denominator.displayBounds) + let numeratorDistance = distanceFromPointToRect(point, numerator.displayBounds()) + let denominatorDistance = distanceFromPointToRect(point, denominator.displayBounds()) if numeratorDistance < denominatorDistance { return MTMathListIndex(atLocation: UInt(range.location), withSubIndex: numerator.closestIndex(to: point), @@ -178,19 +178,19 @@ extension MTFractionDisplay { } @objc(caretPositionForIndex:) - public func caretPosition(for index: MTMathListIndex) -> CGPoint { + public override func caretPosition(for index: MTMathListIndex) -> CGPoint { assert(index.subIndexType == .subIndexTypeNone) return CGPoint(x: position.x, y: position.y) } @objc(highlightCharacterAtIndex:color:) - public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) { assert(index.subIndexType == .subIndexTypeNone) highlight(with: color) } @objc(highlightWithColor:) - public func highlight(with color: MTColor) { + public override func highlight(with color: MTColor) { numerator.highlight(with: color) denominator.highlight(with: color) } @@ -211,18 +211,18 @@ extension MTFractionDisplay { extension MTRadicalDisplay { @objc(closestIndexToPoint:) - public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { if point.x < position.x - pixelDelta { return MTMathListIndex.level0Index(UInt(range.location)) } else if point.x > position.x + width + pixelDelta { return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) } else { - let degreeDistance = distanceFromPointToRect(point, degree.displayBounds) - let radicandDistance = distanceFromPointToRect(point, radicand.displayBounds) + let degreeDistance = distanceFromPointToRect(point, degree!.displayBounds()) + let radicandDistance = distanceFromPointToRect(point, radicand.displayBounds()) if degreeDistance < radicandDistance { return MTMathListIndex(atLocation: UInt(range.location), - withSubIndex: degree.closestIndex(to: point), + withSubIndex: degree?.closestIndex(to: point), type: .subIndexTypeDegree) } else { return MTMathListIndex(atLocation: UInt(range.location), @@ -233,19 +233,19 @@ extension MTRadicalDisplay { } @objc(caretPositionForIndex:) - public func caretPosition(for index: MTMathListIndex) -> CGPoint { + public override func caretPosition(for index: MTMathListIndex) -> CGPoint { assert(index.subIndexType == .subIndexTypeNone) return CGPoint(x: position.x, y: position.y) } @objc(highlightCharacterAtIndex:color:) - public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) { assert(index.subIndexType == .subIndexTypeNone) highlight(with: color) } @objc(highlightWithColor:) - public func highlight(with color: MTColor) { + public override func highlight(with color: MTColor) { radicand.highlight(with: color) } @@ -265,7 +265,7 @@ extension MTRadicalDisplay { extension MTMathListDisplay { @objc(closestIndexToPoint:) - public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { let translatedPoint = CGPoint(x: point.x - position.x, y: point.y - position.y) var closest: MTDisplay? @@ -273,7 +273,7 @@ extension MTMathListDisplay { var minDistance = CGFloat.greatestFiniteMagnitude for atom in subDisplays { - let bounds = atom.displayBounds + let bounds = atom.displayBounds() if bounds.origin.x - pixelDelta <= translatedPoint.x && translatedPoint.x <= bounds.maxX + pixelDelta { xbounds.append(atom) } @@ -297,7 +297,7 @@ extension MTMathListDisplay { } else if xbounds.count == 1 { atomWithPoint = xbounds[0] if translatedPoint.x >= width - pixelDelta, - translatedPoint.y <= atomWithPoint!.displayBounds.minY - pixelDelta { + translatedPoint.y <= atomWithPoint!.displayBounds().minY - pixelDelta { return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) } } else { @@ -309,9 +309,9 @@ extension MTMathListDisplay { let index = atomWithPoint.closestIndex(to: translatedPoint) if let closestLine = atomWithPoint as? MTMathListDisplay { - assert(closestLine.type == .linePositionSubscript || closestLine.type == .linePositionSuperscript, + assert(closestLine.type == .subscript || closestLine.type == .superscript, "MTLine type regular inside an MTLine - shouldn't happen") - let type: MTMathListSubIndexType = (closestLine.type == .linePositionSubscript) ? .subIndexTypeSubscript : .subIndexTypeSuperscript + let type: MTMathListSubIndexType = (closestLine.type == .subscript) ? .subIndexTypeSubscript : .subIndexTypeSuperscript let lineIndex = Int(closestLine.index) guard lineIndex != NSNotFound else { return nil } return MTMathListIndex(atLocation: UInt(lineIndex), withSubIndex: index, type: type) @@ -332,8 +332,8 @@ extension MTMathListDisplay { for atom in subDisplays { if let lineAtom = atom as? MTMathListDisplay, Int(index.atomIndex) == Int(lineAtom.index) { - if (lineAtom.type == .linePositionSubscript && index.subIndexType == .subIndexTypeSubscript) - || (lineAtom.type == .linePositionSuperscript && index.subIndexType == .subIndexTypeSuperscript) { + if (lineAtom.type == .subscript && index.subIndexType == .subIndexTypeSubscript) + || (lineAtom.type == .superscript && index.subIndexType == .subIndexTypeSuperscript) { return lineAtom } } @@ -371,7 +371,7 @@ extension MTMathListDisplay { } @objc(caretPositionForIndex:) - public func caretPosition(for index: MTMathListIndex) -> CGPoint { + public override func caretPosition(for index: MTMathListIndex) -> CGPoint { var pos = invalidPosition if Int(index.atomIndex) == NSMaxRange(range) { @@ -400,7 +400,7 @@ extension MTMathListDisplay { } @objc(highlightCharacterAtIndex:color:) - public func highlightCharacter(at index: MTMathListIndex, color: MTColor) { + public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) { if NSLocationInRange(Int(index.atomIndex), range), let atom = subAtom(for: index) { if index.subIndexType == .subIndexTypeNucleus || index.subIndexType == .subIndexTypeNone { atom.highlightCharacter(at: index, color: color) @@ -411,7 +411,7 @@ extension MTMathListDisplay { } @objc(highlightWithColor:) - public func highlight(with color: MTColor) { + public override func highlight(with color: MTColor) { for atom in subDisplays { atom.highlight(with: color) } diff --git a/mathEditorSwift/Internal/MTMathList+Editing.swift b/mathEditorSwift/Internal/MTMathList+Editing.swift index 136c069..2f94982 100644 --- a/mathEditorSwift/Internal/MTMathList+Editing.swift +++ b/mathEditorSwift/Internal/MTMathList+Editing.swift @@ -23,7 +23,7 @@ extension MTMathList { switch index.subIndexType { case .subIndexTypeNone: - insert(atom, at: index.atomIndex) + insertAtom(atom, at: index.atomIndex) case .subIndexTypeNucleus: let atomIndex = Int(index.atomIndex) @@ -38,7 +38,7 @@ extension MTMathList { atom.superScript = currentAtom.superScript currentAtom.subScript = nil currentAtom.superScript = nil - insert(atom, at: index.atomIndex + subIndex.atomIndex) + insertAtom(atom, at: index.atomIndex + subIndex.atomIndex) case .subIndexTypeDegree, .subIndexTypeRadicand: let atomIndex = Int(index.atomIndex) @@ -48,9 +48,9 @@ extension MTMathList { } guard let subIndex = index.sub else { return } if index.subIndexType == .subIndexTypeDegree { - radical.degree.insert(atom, atListIndex: subIndex) + radical.degree?.insert(atom, atListIndex: subIndex) } else { - radical.radicand.insert(atom, atListIndex: subIndex) + radical.radicand?.insert(atom, atListIndex: subIndex) } case .subIndexTypeDenominator, .subIndexTypeNumerator: @@ -131,9 +131,9 @@ extension MTMathList { } guard let subIndex = index.sub else { return } if index.subIndexType == .subIndexTypeDegree { - radical.degree.removeAtom(atListIndex: subIndex) + radical.degree?.removeAtom(atListIndex: subIndex) } else { - radical.radicand.removeAtom(atListIndex: subIndex) + radical.radicand?.removeAtom(atListIndex: subIndex) } case .subIndexTypeDenominator, .subIndexTypeNumerator: @@ -190,9 +190,9 @@ extension MTMathList { } guard let subIndexRange = range.subIndex() else { return } if start.subIndexType == .subIndexTypeDegree { - radical.degree.removeAtoms(inListIndexRange: subIndexRange) + radical.degree?.removeAtoms(inListIndexRange: subIndexRange) } else { - radical.radicand.removeAtoms(inListIndexRange: subIndexRange) + radical.radicand?.removeAtoms(inListIndexRange: subIndexRange) } case .subIndexTypeDenominator, .subIndexTypeNumerator: @@ -254,9 +254,9 @@ extension MTMathList { } guard let subIndex = index.sub else { return nil } if index.subIndexType == .subIndexTypeDegree { - return radical.degree.atom(atListIndex: subIndex) + return radical.degree?.atom(atListIndex: subIndex) } else { - return radical.radicand.atom(atListIndex: subIndex) + return radical.radicand?.atom(atListIndex: subIndex) } case .subIndexTypeNumerator, .subIndexTypeDenominator: From 92a877449dd1d84045ce886bba1122580c8d071e Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Tue, 24 Mar 2026 14:51:25 +0000 Subject: [PATCH 094/133] More removal --- mathEditor/editor/MTEditableMathLabel.m | 1 - mathEditor/internal/MTMathList+Editing.h | 26 ------------------------ 2 files changed, 27 deletions(-) delete mode 100644 mathEditor/internal/MTMathList+Editing.h diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index dd4b675..190171c 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -16,7 +16,6 @@ #import "MTMathAtomFactory.h" #import "MTCaretView.h" #import "MTTapGestureRecognizer.h" -#import "MTMathList+Editing.h" #import "MTUnicode.h" #import "MTMathListBuilder.h" diff --git a/mathEditor/internal/MTMathList+Editing.h b/mathEditor/internal/MTMathList+Editing.h deleted file mode 100644 index 00de7c1..0000000 --- a/mathEditor/internal/MTMathList+Editing.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MTMathList+Editing.h -// -// Created by Kostub Deshmukh on 9/5/13. -// Copyright (C) 2013 MathChat -// -// This software may be modified and distributed under the terms of the -// MIT license. See the LICENSE file for details. -// - -#import "MTMathList.h" -#import "MTMathListIndex.h" - -@interface MTMathList (Editing) - -- (void) insertAtom:(MTMathAtom *)atom atListIndex:(MTMathListIndex*) index; - -- (void) removeAtomAtListIndex:(MTMathListIndex *)index; - -- (void) removeAtomsInListIndexRange:(MTMathListRange*) range; - -// Get the atom at the given index. If there is none, or index is invalid returns nil. -- (MTMathAtom*) atomAtListIndex:(MTMathListIndex*) index; - - -@end From 0d384d4f234fa12675cf541b20feabd2d417cbc4 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 19:31:13 +0000 Subject: [PATCH 095/133] Restore Obj-C migration comments in Swift editing extensions --- .../Internal/MTDisplay+Editing.swift | 25 +++++++++++++++++++ .../Internal/MTMathList+Editing.swift | 9 +++++++ 2 files changed, 34 insertions(+) diff --git a/mathEditorSwift/Internal/MTDisplay+Editing.swift b/mathEditorSwift/Internal/MTDisplay+Editing.swift index e611284..854f85a 100644 --- a/mathEditorSwift/Internal/MTDisplay+Editing.swift +++ b/mathEditorSwift/Internal/MTDisplay+Editing.swift @@ -10,6 +10,7 @@ import Foundation import iosMath private let invalidPosition = CGPoint(x: -1, y: -1) +// Number of pixels outside the bounds to still consider a point as part of that bounds. private let pixelDelta: CGFloat = 2 private func codePointIndexToStringIndex(_ str: String, _ codePointIndex: UInt) -> Int { @@ -34,6 +35,7 @@ private func codePointIndexToStringIndex(_ str: String, _ codePointIndex: UInt) } private func distanceFromPointToRect(_ point: CGPoint, _ rect: CGRect) -> CGFloat { + // Manhattan distance from the point to the nearest rectangle boundary. var distance: CGFloat = 0 if point.x < rect.origin.x { @@ -52,6 +54,7 @@ private func distanceFromPointToRect(_ point: CGPoint, _ rect: CGRect) -> CGFloa } extension MTDisplay { + // Empty implementations for the base class. @objc(closestIndexToPoint:) public func closestIndex(to point: CGPoint) -> MTMathListIndex? { nil @@ -72,12 +75,14 @@ extension MTDisplay { extension MTCTLineDisplay { @objc(closestIndexToPoint:) public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { + // Convert the point to the reference of the CTLine. let relativePoint = CGPoint(x: point.x - position.x, y: point.y - position.y) let idx = CTLineGetStringIndexForPosition(line, relativePoint) if idx == kCFNotFound { return nil } + // The CoreText index is UTF-16 based. Convert to a math-list atom index. let mlIndex = convertToMathListIndex(UInt(idx)) assert(mlIndex <= UInt(range.length), "Returned index out of range: \(idx)") return MTMathListIndex.level0Index(UInt(range.location) + mlIndex) @@ -130,6 +135,7 @@ extension MTCTLineDisplay { @objc(convertToMathListIndex:) public func convertToMathListIndex(_ strIndex: UInt) -> UInt { + // A single math atom may map to multiple UTF-16 code units. var strLenCovered: UInt = 0 for mlIndex in 0..= strIndex { @@ -138,6 +144,7 @@ extension MTCTLineDisplay { guard let atom = atoms[mlIndex] as? MTMathAtom else { continue } strLenCovered += UInt(atom.nucleus.count) } + // By the end we should have covered all characters that can be addressed. assert(strLenCovered >= strIndex, "StrIndex should not be more than the len covered") return UInt(atoms.count) } @@ -158,6 +165,7 @@ extension MTCTLineDisplay { extension MTFractionDisplay { @objc(closestIndexToPoint:) public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { + // We can be before the fraction, inside the fraction, or after it. if point.x < position.x - pixelDelta { return MTMathListIndex.level0Index(UInt(range.location)) } else if point.x > position.x + width + pixelDelta { @@ -179,6 +187,7 @@ extension MTFractionDisplay { @objc(caretPositionForIndex:) public override func caretPosition(for index: MTMathListIndex) -> CGPoint { + // Draw a caret before the fraction. assert(index.subIndexType == .subIndexTypeNone) return CGPoint(x: position.x, y: position.y) } @@ -212,6 +221,7 @@ extension MTFractionDisplay { extension MTRadicalDisplay { @objc(closestIndexToPoint:) public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { + // We can be before the radical, inside the radical, or after it. if point.x < position.x - pixelDelta { return MTMathListIndex.level0Index(UInt(range.location)) } else if point.x > position.x + width + pixelDelta { @@ -234,6 +244,7 @@ extension MTRadicalDisplay { @objc(caretPositionForIndex:) public override func caretPosition(for index: MTMathListIndex) -> CGPoint { + // Draw a caret before the radical. assert(index.subIndexType == .subIndexTypeNone) return CGPoint(x: position.x, y: position.y) } @@ -266,6 +277,7 @@ extension MTRadicalDisplay { extension MTMathListDisplay { @objc(closestIndexToPoint:) public override func closestIndex(to point: CGPoint) -> MTMathListIndex? { + // Subdisplay origins are relative to this display's position. let translatedPoint = CGPoint(x: point.x - position.x, y: point.y - position.y) var closest: MTDisplay? @@ -288,16 +300,20 @@ extension MTMathListDisplay { let atomWithPoint: MTDisplay? if xbounds.isEmpty { if translatedPoint.x <= -pixelDelta { + // Far to the left. return MTMathListIndex.level0Index(UInt(range.location)) } else if translatedPoint.x >= width + pixelDelta { + // Far to the right. return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) } else { + // Within mathlist bounds but not in any x-range; use nearest subdisplay. atomWithPoint = closest } } else if xbounds.count == 1 { atomWithPoint = xbounds[0] if translatedPoint.x >= width - pixelDelta, translatedPoint.y <= atomWithPoint!.displayBounds().minY - pixelDelta { + // Near the end but too high for this atom; place caret at end of list. return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) } } else { @@ -311,11 +327,13 @@ extension MTMathListDisplay { if let closestLine = atomWithPoint as? MTMathListDisplay { assert(closestLine.type == .subscript || closestLine.type == .superscript, "MTLine type regular inside an MTLine - shouldn't happen") + // Subscript/superscript line: wrap the returned index as a nested sub-index. let type: MTMathListSubIndexType = (closestLine.type == .subscript) ? .subIndexTypeSubscript : .subIndexTypeSuperscript let lineIndex = Int(closestLine.index) guard lineIndex != NSNotFound else { return nil } return MTMathListIndex(atLocation: UInt(lineIndex), withSubIndex: index, type: type) } else if atomWithPoint.hasScript, let index { + // If we landed at atom end, caret should be before scripts, not after them. if Int(index.atomIndex) == NSMaxRange(atomWithPoint.range) { return MTMathListIndex(atLocation: index.atomIndex - 1, withSubIndex: MTMathListIndex.level0Index(1), @@ -328,6 +346,7 @@ extension MTMathListDisplay { @objc(subAtomForIndex:) public func subAtom(for index: MTMathListIndex) -> MTDisplay? { + // Sub/superscripts are represented as MTMathListDisplay entries in subDisplays. if index.subIndexType == .subIndexTypeSuperscript || index.subIndexType == .subIndexTypeSubscript { for atom in subDisplays { if let lineAtom = atom as? MTMathListDisplay, @@ -341,6 +360,7 @@ extension MTMathListDisplay { } else { for atom in subDisplays { if !(atom is MTMathListDisplay) && NSLocationInRange(Int(index.atomIndex), atom.range) { + // Found the display that covers the requested index. switch index.subIndexType { case .subIndexTypeNone, .subIndexTypeNucleus: return atom @@ -375,6 +395,7 @@ extension MTMathListDisplay { var pos = invalidPosition if Int(index.atomIndex) == NSMaxRange(range) { + // Special-case right edge of the range. pos = CGPoint(x: width, y: 0) } else if NSLocationInRange(Int(index.atomIndex), range) { guard let atom = subAtom(for: index) else { return invalidPosition } @@ -385,6 +406,7 @@ extension MTMathListDisplay { } else if index.subIndexType == .subIndexTypeNone { pos = atom.caretPosition(for: index) } else { + // Recurse into nested substructures. guard let subIndex = index.sub else { return invalidPosition } pos = atom.caretPosition(for: subIndex) } @@ -393,9 +415,11 @@ extension MTMathListDisplay { } if pos == invalidPosition { + // Position could not be resolved by subdisplays. return pos } + // Convert from local coordinates before returning. return CGPoint(x: pos.x + position.x, y: pos.y + position.y) } @@ -405,6 +429,7 @@ extension MTMathListDisplay { if index.subIndexType == .subIndexTypeNucleus || index.subIndexType == .subIndexTypeNone { atom.highlightCharacter(at: index, color: color) } else if let subIndex = index.sub { + // Recurse into nested substructures. atom.highlightCharacter(at: subIndex, color: color) } } diff --git a/mathEditorSwift/Internal/MTMathList+Editing.swift b/mathEditorSwift/Internal/MTMathList+Editing.swift index 2f94982..4dc6b27 100644 --- a/mathEditorSwift/Internal/MTMathList+Editing.swift +++ b/mathEditorSwift/Internal/MTMathList+Editing.swift @@ -43,6 +43,7 @@ extension MTMathList { case .subIndexTypeDegree, .subIndexTypeRadicand: let atomIndex = Int(index.atomIndex) guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else { + // Not radical, quit. assertionFailure("No radical found at index \(index.atomIndex)") return } @@ -56,6 +57,7 @@ extension MTMathList { case .subIndexTypeDenominator, .subIndexTypeNumerator: let atomIndex = Int(index.atomIndex) guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else { + // Not a fraction, quit. assertionFailure("No fraction found at index \(index.atomIndex)") return } @@ -120,12 +122,14 @@ extension MTMathList { previous.subScript = currentAtom.subScript removeAtom(at: index.atomIndex) } else { + // No previous atom, or the previous atom already has a sub/superscript. currentAtom.nucleus = "" } case .subIndexTypeRadicand, .subIndexTypeDegree: let atomIndex = Int(index.atomIndex) guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else { + // Not radical, quit. assertionFailure("No radical found at index \(index.atomIndex)") return } @@ -139,6 +143,7 @@ extension MTMathList { case .subIndexTypeDenominator, .subIndexTypeNumerator: let atomIndex = Int(index.atomIndex) guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else { + // Not a fraction, quit. assertionFailure("No fraction found at index \(index.atomIndex)") return } @@ -185,6 +190,7 @@ extension MTMathList { case .subIndexTypeRadicand, .subIndexTypeDegree: let atomIndex = Int(start.atomIndex) guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else { + // Not radical, quit. assertionFailure("No radical found at index \(start.atomIndex)") return } @@ -198,6 +204,7 @@ extension MTMathList { case .subIndexTypeDenominator, .subIndexTypeNumerator: let atomIndex = Int(start.atomIndex) guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else { + // Not a fraction, quit. assertionFailure("No fraction found at index \(start.atomIndex)") return } @@ -250,6 +257,7 @@ extension MTMathList { case .subIndexTypeRadicand, .subIndexTypeDegree: guard let radical = atom as? MTRadical, atom.type == .radical else { + // No radical at this index. return nil } guard let subIndex = index.sub else { return nil } @@ -261,6 +269,7 @@ extension MTMathList { case .subIndexTypeNumerator, .subIndexTypeDenominator: guard let frac = atom as? MTFraction, atom.type == .fraction else { + // No fraction at this index. return nil } guard let subIndex = index.sub else { return nil } From 3f7569dea0e13384d8228b5ea4dbab891c6adab4 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 22:24:47 +0000 Subject: [PATCH 096/133] Version --- .../ContentView.swift | 3 +- mathEditorSwift/Internal/MTCancelView.swift | 16 +- .../Internal/MTCaretViewSwift.swift | 296 +++++++ .../Internal/MTDisplay+Editing.swift | 80 +- .../Internal/MTMathList+Editing.swift | 20 +- .../Internal/MTTapGestureRecognizer.swift | 8 +- mathEditorSwift/Internal/MTView/MTView.swift | 26 +- .../MTEditableMathLabelSwift.swift | 806 ++++++++++++++++++ 8 files changed, 1191 insertions(+), 64 deletions(-) create mode 100644 mathEditorSwift/Internal/MTCaretViewSwift.swift create mode 100644 mathEditorSwift/MTEditableMathLabelSwift.swift diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index 4ef911f..f82a0bd 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -1,6 +1,7 @@ // Copyright © 2025 Snap, Inc. All rights reserved. import MathEditor +import MathEditorSwift import SwiftUI struct ContentView: View { @@ -41,7 +42,7 @@ struct ContentView: View { typealias UIViewType = NSView func makeNSView(context: Context) -> NSView { - let mathLabel = MTEditableMathLabel() + let mathLabel = MTEditableMathLabelSwift() mathLabel.backgroundColor = .clear mathLabel.caretColor = NSColor.labelColor mathLabel.textColor = NSColor.labelColor diff --git a/mathEditorSwift/Internal/MTCancelView.swift b/mathEditorSwift/Internal/MTCancelView.swift index 50ac9fd..11e7d64 100644 --- a/mathEditorSwift/Internal/MTCancelView.swift +++ b/mathEditorSwift/Internal/MTCancelView.swift @@ -7,15 +7,15 @@ public final class MTCancelView: MTView { @objc public init(target: AnyObject, action: Selector) { #if canImport(UIKit) - let image = MTImage(systemName: "xmark.circle")?.withRenderingMode(.alwaysTemplate) - imageView = MTImageView(image: image) - imageView.contentMode = .scaleAspectFit - imageView.tintColor = .secondaryLabel + let image = MTImage(systemName: "xmark.circle")?.withRenderingMode(.alwaysTemplate) + imageView = MTImageView(image: image) + imageView.contentMode = .scaleAspectFit + imageView.tintColor = .secondaryLabel #else - imageView = MTImageView(frame: .zero) - imageView.image = MTImage(systemSymbolName: "xmark.circle", accessibilityDescription: nil) - imageView.imageScaling = .scaleProportionallyUpOrDown - imageView.contentTintColor = .secondaryLabelColor + imageView = MTImageView(frame: .zero) + imageView.image = MTImage(systemSymbolName: "xmark.circle", accessibilityDescription: nil) + imageView.imageScaling = .scaleProportionallyUpOrDown + imageView.contentTintColor = .secondaryLabelColor #endif super.init(frame: .zero) diff --git a/mathEditorSwift/Internal/MTCaretViewSwift.swift b/mathEditorSwift/Internal/MTCaretViewSwift.swift new file mode 100644 index 0000000..e2cc261 --- /dev/null +++ b/mathEditorSwift/Internal/MTCaretViewSwift.swift @@ -0,0 +1,296 @@ +import Foundation + +#if canImport(UIKit) + import UIKit +#elseif canImport(AppKit) + import AppKit +#endif + +private let initialBlinkDelay: TimeInterval = 0.7 +private let blinkRate: TimeInterval = 0.5 +private let caretFontSize: CGFloat = 30 +private let caretAscent: CGFloat = 25 +private let caretWidth: CGFloat = 3 +private let caretDescent: CGFloat = 7 +private let caretHandleWidth: CGFloat = 15 +private let caretHandleDescent: CGFloat = 8 +private let caretHandleHeight: CGFloat = 20 +private let caretHandleHitAreaSize: CGFloat = 44 + +private func caretHeight() -> CGFloat { + caretAscent + caretDescent +} + +private final class MTCaretHandleSwift: MTView { + weak var label: MTEditableMathLabelSwift? + + var color: MTColor? { + didSet { + baseColor = color?.withAlphaComponent(0.7) + setNeedsDisplayCompat() + } + } + + private var path = MTBezierPath() + private var baseColor: MTColor? + + override init(frame: CGRect) { + super.init(frame: frame) + path = createHandlePath() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func createHandlePath() -> MTBezierPath { + let path = MTBezierPath() + let size = bounds.size + path.move(to: CGPoint(x: size.width / 2, y: 0)) + path.addLine(to: CGPoint(x: size.width, y: size.height / 4)) + path.addLine(to: CGPoint(x: size.width, y: size.height)) + path.addLine(to: CGPoint(x: 0, y: size.height)) + path.addLine(to: CGPoint(x: 0, y: size.height / 4)) + path.close() + return path + } + + private func interactionBegan() { + baseColor = baseColor?.withAlphaComponent(1.0) + setNeedsDisplayCompat() + } + + private func interactionEnded() { + baseColor = baseColor?.withAlphaComponent(0.6) + setNeedsDisplayCompat() + } + + private func handleDrag(localPoint: CGPoint) { + let caretPoint = CGPoint(x: localPoint.x, y: localPoint.y - frame.origin.y) + guard let label else { return } + let labelPoint = convert(caretPoint, to: label) + label.moveCaret(to: labelPoint) + } + + private func hitArea() -> CGRect { + let size = bounds.size + return CGRect( + x: (size.width - caretHandleHitAreaSize) / 2, + y: (size.height - caretHandleHitAreaSize) / 2, + width: caretHandleHitAreaSize, + height: caretHandleHitAreaSize + ) + } + + #if canImport(UIKit) + public override func draw(_ rect: CGRect) { + baseColor?.setFill() + path.fill() + } + + public override func layoutSubviews() { + super.layoutSubviews() + path = createHandlePath() + } + + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { + interactionBegan() + } + + public override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + interactionEnded() + } + + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { return } + handleDrag(localPoint: touch.location(in: self)) + } + + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + interactionEnded() + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + hitArea().contains(point) + } + #else + public override var isFlipped: Bool { true } + + public override func draw(_ dirtyRect: NSRect) { + baseColor?.setFill() + path.fill() + } + + public override func layout() { + super.layout() + path = createHandlePath() + } + + public override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + true + } + + public override func mouseDown(with event: NSEvent) { + interactionBegan() + } + + public override func mouseDragged(with event: NSEvent) { + handleDrag(localPoint: convert(event.locationInWindow, from: nil)) + } + + public override func mouseUp(with event: NSEvent) { + interactionEnded() + } + + public override func hitTest(_ point: NSPoint) -> NSView? { + guard !isHidden else { return nil } + let localPoint = convert(point, from: superview) + return hitArea().contains(localPoint) ? self : nil + } + #endif + + private func setNeedsDisplayCompat() { + #if canImport(UIKit) + setNeedsDisplay() + #else + needsDisplay = true + #endif + } +} + +public final class MTCaretViewSwift: MTView { + public var caretColor: MTColor? { + didSet { + handle.color = caretColor + blinker.backgroundColor = caretColor + } + } + + private var blinkTimer: Timer? + private let blinker = MTView(frame: .zero) + private let handle: MTCaretHandleSwift + private var scale: CGFloat + + init(editor: MTEditableMathLabelSwift) { + scale = editor.fontSize / caretFontSize + handle = MTCaretHandleSwift( + frame: CGRect( + x: 0, + y: 0, + width: caretHandleWidth * scale, + height: caretHandleHeight * scale + )) + super.init(frame: .zero) + + blinker.backgroundColor = caretColor + addSubview(blinker) + + handle.backgroundColor = .clear + handle.isHidden = true + handle.label = editor + addSubview(handle) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setPosition(_ position: CGPoint) { + frame = CGRect(x: position.x, y: position.y - caretAscent * scale, width: 0, height: 0) + } + + func setFontSize(_ fontSize: CGFloat) { + scale = fontSize / caretFontSize + setNeedsLayoutCompat() + } + + func showHandle(_ show: Bool) { + handle.isHidden = !show + } + + func delayBlink() { + isHidden = false + blinker.isHidden = false + blinkTimer?.fireDate = Date(timeIntervalSinceNow: initialBlinkDelay) + } + + private func blink() { + blinker.isHidden.toggle() + } + + private func doLayout() { + blinker.frame = CGRect(x: 0, y: 0, width: caretWidth * scale, height: caretHeight() * scale) + handle.frame = CGRect( + x: -((caretHandleWidth - caretWidth) * scale / 2), + y: (caretHeight() + caretHandleDescent) * scale, + width: caretHandleWidth * scale, + height: caretHandleHeight * scale + ) + } + + private func startBlinkingIfNeeded() { + guard superview != nil else { + blinkTimer?.invalidate() + blinkTimer = nil + return + } + if blinkTimer == nil { + blinkTimer = Timer.scheduledTimer(withTimeInterval: blinkRate, repeats: true) { + [weak self] _ in + self?.blink() + } + } + delayBlink() + } + + deinit { + blinkTimer?.invalidate() + } + + #if canImport(UIKit) + public override func didMoveToSuperview() { + super.didMoveToSuperview() + isHidden = false + startBlinkingIfNeeded() + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if !handle.isHidden { + return handle.point(inside: convert(point, to: handle), with: event) + } + return super.point(inside: point, with: event) + } + + public override func layoutSubviews() { + super.layoutSubviews() + doLayout() + } + #else + public override var isFlipped: Bool { true } + + public override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + isHidden = false + startBlinkingIfNeeded() + } + + public override func layout() { + super.layout() + doLayout() + } + + public override func hitTest(_ point: NSPoint) -> NSView? { + hitTestOutsideBounds(point) + } + #endif + + private func setNeedsLayoutCompat() { + #if canImport(UIKit) + setNeedsLayout() + #else + needsLayout = true + #endif + } +} diff --git a/mathEditorSwift/Internal/MTDisplay+Editing.swift b/mathEditorSwift/Internal/MTDisplay+Editing.swift index 854f85a..5477758 100644 --- a/mathEditorSwift/Internal/MTDisplay+Editing.swift +++ b/mathEditorSwift/Internal/MTDisplay+Editing.swift @@ -90,7 +90,8 @@ extension MTCTLineDisplay { @objc(caretPositionForIndex:) public override func caretPosition(for index: MTMathListIndex) -> CGPoint { - assert(index.subIndexType == .subIndexTypeNone, "Index in a CTLineDisplay cannot have sub indexes.") + assert( + index.subIndexType == .subIndexTypeNone, "Index in a CTLineDisplay cannot have sub indexes.") let offset: CGFloat if Int(index.atomIndex) == NSMaxRange(range) { @@ -113,23 +114,26 @@ extension MTCTLineDisplay { assertionFailure("Nucleus highlighting not supported yet") } - let charIndex = codePointIndexToStringIndex(attributedString.string, index.atomIndex - UInt(range.location)) + let charIndex = codePointIndexToStringIndex( + attributedString.string, index.atomIndex - UInt(range.location)) assert(charIndex != NSNotFound) let attrStr = NSMutableAttributedString(attributedString: attributedString) let seqRange = (attrStr.string as NSString).rangeOfComposedCharacterSequence(at: charIndex) - attrStr.addAttribute(kCTForegroundColorAttributeName as NSAttributedString.Key, - value: color.cgColor, - range: seqRange) + attrStr.addAttribute( + kCTForegroundColorAttributeName as NSAttributedString.Key, + value: color.cgColor, + range: seqRange) attributedString = attrStr } @objc(highlightWithColor:) public override func highlight(with color: MTColor) { let attrStr = NSMutableAttributedString(attributedString: attributedString) - attrStr.addAttribute(kCTForegroundColorAttributeName as NSAttributedString.Key, - value: color.cgColor, - range: NSRange(location: 0, length: attrStr.length)) + attrStr.addAttribute( + kCTForegroundColorAttributeName as NSAttributedString.Key, + value: color.cgColor, + range: NSRange(location: 0, length: attrStr.length)) attributedString = attrStr } @@ -174,13 +178,15 @@ extension MTFractionDisplay { let numeratorDistance = distanceFromPointToRect(point, numerator.displayBounds()) let denominatorDistance = distanceFromPointToRect(point, denominator.displayBounds()) if numeratorDistance < denominatorDistance { - return MTMathListIndex(atLocation: UInt(range.location), - withSubIndex: numerator.closestIndex(to: point), - type: .subIndexTypeNumerator) + return MTMathListIndex( + atLocation: UInt(range.location), + withSubIndex: numerator.closestIndex(to: point), + type: .subIndexTypeNumerator) } else { - return MTMathListIndex(atLocation: UInt(range.location), - withSubIndex: denominator.closestIndex(to: point), - type: .subIndexTypeDenominator) + return MTMathListIndex( + atLocation: UInt(range.location), + withSubIndex: denominator.closestIndex(to: point), + type: .subIndexTypeDenominator) } } } @@ -231,13 +237,15 @@ extension MTRadicalDisplay { let radicandDistance = distanceFromPointToRect(point, radicand.displayBounds()) if degreeDistance < radicandDistance { - return MTMathListIndex(atLocation: UInt(range.location), - withSubIndex: degree?.closestIndex(to: point), - type: .subIndexTypeDegree) + return MTMathListIndex( + atLocation: UInt(range.location), + withSubIndex: degree?.closestIndex(to: point), + type: .subIndexTypeDegree) } else { - return MTMathListIndex(atLocation: UInt(range.location), - withSubIndex: radicand.closestIndex(to: point), - type: .subIndexTypeRadicand) + return MTMathListIndex( + atLocation: UInt(range.location), + withSubIndex: radicand.closestIndex(to: point), + type: .subIndexTypeRadicand) } } } @@ -286,7 +294,9 @@ extension MTMathListDisplay { for atom in subDisplays { let bounds = atom.displayBounds() - if bounds.origin.x - pixelDelta <= translatedPoint.x && translatedPoint.x <= bounds.maxX + pixelDelta { + if bounds.origin.x - pixelDelta <= translatedPoint.x + && translatedPoint.x <= bounds.maxX + pixelDelta + { xbounds.append(atom) } @@ -312,7 +322,8 @@ extension MTMathListDisplay { } else if xbounds.count == 1 { atomWithPoint = xbounds[0] if translatedPoint.x >= width - pixelDelta, - translatedPoint.y <= atomWithPoint!.displayBounds().minY - pixelDelta { + translatedPoint.y <= atomWithPoint!.displayBounds().minY - pixelDelta + { // Near the end but too high for this atom; place caret at end of list. return MTMathListIndex.level0Index(UInt(NSMaxRange(range))) } @@ -325,19 +336,22 @@ extension MTMathListDisplay { let index = atomWithPoint.closestIndex(to: translatedPoint) if let closestLine = atomWithPoint as? MTMathListDisplay { - assert(closestLine.type == .subscript || closestLine.type == .superscript, - "MTLine type regular inside an MTLine - shouldn't happen") + assert( + closestLine.type == .subscript || closestLine.type == .superscript, + "MTLine type regular inside an MTLine - shouldn't happen") // Subscript/superscript line: wrap the returned index as a nested sub-index. - let type: MTMathListSubIndexType = (closestLine.type == .subscript) ? .subIndexTypeSubscript : .subIndexTypeSuperscript + let type: MTMathListSubIndexType = + (closestLine.type == .subscript) ? .subIndexTypeSubscript : .subIndexTypeSuperscript let lineIndex = Int(closestLine.index) guard lineIndex != NSNotFound else { return nil } return MTMathListIndex(atLocation: UInt(lineIndex), withSubIndex: index, type: type) } else if atomWithPoint.hasScript, let index { // If we landed at atom end, caret should be before scripts, not after them. if Int(index.atomIndex) == NSMaxRange(atomWithPoint.range) { - return MTMathListIndex(atLocation: index.atomIndex - 1, - withSubIndex: MTMathListIndex.level0Index(1), - type: .subIndexTypeNucleus) + return MTMathListIndex( + atLocation: index.atomIndex - 1, + withSubIndex: MTMathListIndex.level0Index(1), + type: .subIndexTypeNucleus) } } @@ -347,12 +361,16 @@ extension MTMathListDisplay { @objc(subAtomForIndex:) public func subAtom(for index: MTMathListIndex) -> MTDisplay? { // Sub/superscripts are represented as MTMathListDisplay entries in subDisplays. - if index.subIndexType == .subIndexTypeSuperscript || index.subIndexType == .subIndexTypeSubscript { + if index.subIndexType == .subIndexTypeSuperscript + || index.subIndexType == .subIndexTypeSubscript + { for atom in subDisplays { if let lineAtom = atom as? MTMathListDisplay, - Int(index.atomIndex) == Int(lineAtom.index) { + Int(index.atomIndex) == Int(lineAtom.index) + { if (lineAtom.type == .subscript && index.subIndexType == .subIndexTypeSubscript) - || (lineAtom.type == .superscript && index.subIndexType == .subIndexTypeSuperscript) { + || (lineAtom.type == .superscript && index.subIndexType == .subIndexTypeSuperscript) + { return lineAtom } } diff --git a/mathEditorSwift/Internal/MTMathList+Editing.swift b/mathEditorSwift/Internal/MTMathList+Editing.swift index 4dc6b27..d6489f2 100644 --- a/mathEditorSwift/Internal/MTMathList+Editing.swift +++ b/mathEditorSwift/Internal/MTMathList+Editing.swift @@ -28,10 +28,12 @@ extension MTMathList { case .subIndexTypeNucleus: let atomIndex = Int(index.atomIndex) guard let currentAtom = atoms[atomIndex] as? MTMathAtom else { return } - assert(currentAtom.subScript != nil || currentAtom.superScript != nil, - "Nuclear fusion is not supported if there are no subscripts or superscripts.") - assert(atom.subScript == nil && atom.superScript == nil, - "Cannot fuse with an atom that already has a subscript or a superscript") + assert( + currentAtom.subScript != nil || currentAtom.superScript != nil, + "Nuclear fusion is not supported if there are no subscripts or superscripts.") + assert( + atom.subScript == nil && atom.superScript == nil, + "Cannot fuse with an atom that already has a subscript or a superscript") guard let subIndex = index.sub else { return } atom.subScript = currentAtom.subScript @@ -109,15 +111,17 @@ extension MTMathList { case .subIndexTypeNucleus: let atomIndex = Int(index.atomIndex) guard let currentAtom = atoms[atomIndex] as? MTMathAtom else { return } - assert(currentAtom.subScript != nil || currentAtom.superScript != nil, - "Nuclear fission is not supported if there are no subscripts or superscripts.") + assert( + currentAtom.subScript != nil || currentAtom.superScript != nil, + "Nuclear fission is not supported if there are no subscripts or superscripts.") var previous: MTMathAtom? if index.atomIndex > 0 { previous = atoms[Int(index.atomIndex - 1)] as? MTMathAtom } if let previous, - previous.subScript == nil, - previous.superScript == nil { + previous.subScript == nil, + previous.superScript == nil + { previous.superScript = currentAtom.superScript previous.subScript = currentAtom.subScript removeAtom(at: index.atomIndex) diff --git a/mathEditorSwift/Internal/MTTapGestureRecognizer.swift b/mathEditorSwift/Internal/MTTapGestureRecognizer.swift index 20bf011..68b65fe 100644 --- a/mathEditorSwift/Internal/MTTapGestureRecognizer.swift +++ b/mathEditorSwift/Internal/MTTapGestureRecognizer.swift @@ -1,9 +1,9 @@ import Foundation #if canImport(UIKit) -import UIKit -public typealias MTTapGestureRecognizer = UITapGestureRecognizer + import UIKit + public typealias MTTapGestureRecognizer = UITapGestureRecognizer #elseif canImport(AppKit) -import AppKit -public typealias MTTapGestureRecognizer = NSClickGestureRecognizer + import AppKit + public typealias MTTapGestureRecognizer = NSClickGestureRecognizer #endif diff --git a/mathEditorSwift/Internal/MTView/MTView.swift b/mathEditorSwift/Internal/MTView/MTView.swift index 167db3b..690469a 100644 --- a/mathEditorSwift/Internal/MTView/MTView.swift +++ b/mathEditorSwift/Internal/MTView/MTView.swift @@ -8,17 +8,19 @@ import Foundation #if canImport(UIKit) -import UIKit -public typealias MTView = UIView -public typealias MTColor = UIColor -public typealias MTBezierPath = UIBezierPath -public typealias MTImage = UIImage -public typealias MTImageView = UIImageView + import UIKit + public typealias MTView = UIView + public typealias MTColor = UIColor + public typealias MTBezierPath = UIBezierPath + public typealias MTImage = UIImage + public typealias MTImageView = UIImageView + public typealias MTEdgeInsets = UIEdgeInsets #elseif canImport(AppKit) -import AppKit -public typealias MTView = NSView -public typealias MTColor = NSColor -public typealias MTBezierPath = NSBezierPath -public typealias MTImage = NSImage -public typealias MTImageView = NSImageView + import AppKit + public typealias MTView = NSView + public typealias MTColor = NSColor + public typealias MTBezierPath = NSBezierPath + public typealias MTImage = NSImage + public typealias MTImageView = NSImageView + public typealias MTEdgeInsets = NSEdgeInsets #endif diff --git a/mathEditorSwift/MTEditableMathLabelSwift.swift b/mathEditorSwift/MTEditableMathLabelSwift.swift new file mode 100644 index 0000000..dbacd56 --- /dev/null +++ b/mathEditorSwift/MTEditableMathLabelSwift.swift @@ -0,0 +1,806 @@ +import Foundation +import iosMath + +#if canImport(UIKit) + import UIKit +#elseif canImport(AppKit) + import AppKit +#endif + +private let greekLowerStart: UInt32 = 0x03B1 +private let greekLowerEnd: UInt32 = 0x03C9 +private let greekCapitalStart: UInt32 = 0x0391 +private let greekCapitalEnd: UInt32 = 0x03A9 + +@objc(MTEditableMathLabelSwift) +public final class MTEditableMathLabelSwift: MTView { + @objc public var mathList: MTMathList = MTMathList() { + didSet { + label.mathList = mathList + insertionIndex = MTMathListIndex.level0Index(UInt(mathList.atoms.count)) + insertionPointChanged() + } + } + + @objc public var highlightColor: MTColor = .systemRed + + @objc public var textColor: MTColor? { + get { label.textColor } + set { label.textColor = newValue ?? label.textColor } + } + + @objc public var caretColor: MTColor? { + get { caretView.caretColor } + set { caretView.caretColor = newValue } + } + + @objc public private(set) var cancelImage: MTCancelView? + @objc public private(set) var caretView: MTCaretViewSwift! + @objc public weak var delegate: NSObjectProtocol? + @objc public weak var keyboard: MTView? + + @objc public var fontSize: CGFloat { + get { label.fontSize } + set { + label.fontSize = newValue + caretView.setFontSize(newValue) + insertionPointChanged() + } + } + + @objc public var contentInsets: MTEdgeInsets { + get { label.contentInsets } + set { label.contentInsets = newValue } + } + + private let label = MTMathUILabel(frame: .zero) + private var tapGestureRecognizer: MTTapGestureRecognizer? + private var insertionIndex: MTMathListIndex? + private var flipTransform = CGAffineTransform.identity + + public override init(frame: CGRect) { + super.init(frame: frame) + initialize() + } + + @available(*, unavailable) + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override var backgroundColor: MTColor? { + didSet { + label.backgroundColor = backgroundColor + } + } + + @objc public func clear() { + mathList = MTMathList() + caretView.showHandle(false) + } + + @objc(highlightCharacterAtIndex:) + public func highlightCharacter(at index: MTMathListIndex) { + layoutLabelIfNeeded() + guard let displayList = label.displayList else { return } + displayList.highlightCharacter(at: index, color: highlightColor) + setNeedsDisplayCompat() + } + + @objc public func clearHighlights() { + setNeedsLayoutCompat() + } + + @objc(moveCaretToPoint:) + public func moveCaret(to point: CGPoint) { + insertionIndex = closestIndex(to: point) + insertionPointChanged() + } + + @objc public func startEditing() { + guard !isFirstResponderCompat else { return } + #if canImport(AppKit) + window?.makeFirstResponder(self) + #else + becomeFirstResponder() + #endif + } + + @objc(enableTap:) + public func enableTap(_ enabled: Bool) { + tapGestureRecognizer?.isEnabled = enabled + } + + @objc(insertMathList:atPoint:) + public func insertMathList(_ list: MTMathList, at point: CGPoint) { + guard let detailedIndex = closestIndex(to: point) else { return } + var index = MTMathListIndex.level0Index(detailedIndex.atomIndex) + for atom in list.atoms { + mathList.insert(atom, atListIndex: index) + index = index.next() + } + label.mathList = mathList + insertionIndex = index + insertionPointChanged() + } + + @objc public func mathDisplaySize() -> CGSize { + label.sizeThatFits(label.bounds.size) + } + + @objc public func doLayout() { + cancelImage?.frame = CGRect( + x: frame.size.width - 55, y: (frame.size.height - 45) / 2, width: 45, height: 45) + let transform = CGAffineTransform(translationX: 0, y: bounds.size.height) + flipTransform = CGAffineTransform(scaleX: 1, y: -1).concatenating(transform) + layoutLabelIfNeeded() + insertionPointChanged() + } + + @objc public func doBecomeFirstResponder() { + if insertionIndex == nil { + insertionIndex = MTMathListIndex.level0Index(UInt(mathList.atoms.count)) + } + performSelector("startedEditing:", on: keyboard, with: self) + insertionPointChanged() + performSelector("didBeginEditing:", on: delegate, with: self) + } + + @objc public func doResignFirstResponder() { + performSelector("finishedEditing:", on: keyboard, with: self) + insertionPointChanged() + performSelector("didEndEditing:", on: delegate, with: self) + } + + @objc(insertText:) + public func insertText(_ string: String) { + if string == "\n" { + performSelector("returnPressed:", on: delegate, with: self) + return + } + + guard !string.isEmpty else { return } + let scalar = string.unicodeScalars.first! + var insertedAtom: MTMathAtom? + + if string.count > 1 { + insertedAtom = MTMathAtomFactory.atom(forLatexSymbolName: string) + } else { + insertedAtom = atom(forCharacter: scalar) + } + + if insertionIndex?.subIndexType == .subIndexTypeDenominator, insertedAtom?.type == .relation { + insertionIndex = insertionIndex?.levelDown()?.next() + } + + switch string { + case String(Character("^")): + handleExponentButton() + case MTSymbolSquareRoot: + handleRadical(withDegreeButtonPressed: false) + case MTSymbolCubeRoot: + handleRadical(withDegreeButtonPressed: true) + case String(Character("_")): + handleSubscriptButton() + case String(Character("/")): + handleSlashButton() + case "()": + removePlaceholderIfPresent() + insertParens() + case "||": + removePlaceholderIfPresent() + insertAbsValue() + default: + if let insertedAtom, let insertionIndex { + if !updatePlaceholderIfPresent(insertedAtom) { + mathList.insert(insertedAtom, atListIndex: insertionIndex) + } + if insertedAtom.type == .fraction { + self.insertionIndex = insertionIndex.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeNumerator) + } else { + self.insertionIndex = insertionIndex.next() + } + } + } + + label.mathList = mathList + insertionPointChanged() + + if isTrigFunction(string) { + insertParens() + } + + performSelector("textModified:", on: delegate, with: self) + } + + @objc public func deleteBackward() { + guard hasText, var previousIndex = insertionIndex?.previous() else { return } + + mathList.removeAtom(atListIndex: previousIndex) + if previousIndex.finalSubIndexType() == MTMathListSubIndexType.subIndexTypeNucleus, + let downIndex = previousIndex.levelDown() + { + if let previous = downIndex.previous() { + previousIndex = previous.levelUp( + withSubIndex: MTMathListIndex.level0Index(1), + type: MTMathListSubIndexType.subIndexTypeNucleus) + } else { + previousIndex = downIndex + } + } + insertionIndex = previousIndex + + if insertionIndex?.isAtBeginningOfLine() == true, + insertionIndex?.subIndexType != .subIndexTypeNone + { + if mathList.atom(atListIndex: insertionIndex) == nil, let insertionIndex { + let atom = MTMathAtomFactory.placeholder() + atom.nucleus = MTSymbolBlackSquare + mathList.insert(atom, atListIndex: insertionIndex) + } + } + + label.mathList = mathList + insertionPointChanged() + performSelector("textModified:", on: delegate, with: self) + } + + @objc public var hasText: Bool { + !mathList.atoms.isEmpty + } + + @objc(closestIndexToPoint:) + public func closestIndex(to point: CGPoint) -> MTMathListIndex? { + layoutLabelIfNeeded() + guard let displayList = label.displayList else { return nil } + return displayList.closestIndex(to: convert(point, to: label)) + } + + @objc(caretRectForIndex:) + public func caretRect(for index: MTMathListIndex) -> CGPoint { + layoutLabelIfNeeded() + guard let displayList = label.displayList else { return .zero } + return displayList.caretPosition(for: index) + } +} + +extension MTEditableMathLabelSwift { + fileprivate func initialize() { + let tap = MTTapGestureRecognizer(target: self, action: #selector(tap(_:))) + addGestureRecognizer(tap) + tapGestureRecognizer = tap + + addSubview(label) + label.pinToSuperview() + label.fontSize = 30 + label.backgroundColor = backgroundColor + #if canImport(UIKit) + label.isUserInteractionEnabled = false + #endif + label.textAlignment = .center + + createCancelImage() + + let transform = CGAffineTransform(translationX: 0, y: bounds.size.height) + flipTransform = CGAffineTransform(scaleX: 1, y: -1).concatenating(transform) + + caretView = MTCaretViewSwift(editor: self) + caretView.caretColor = MTColor(white: 0.1, alpha: 1.0) + + highlightColor = MTColor.systemRed + bringSubviewToFront(cancelImage!) + mathList = MTMathList() + } + + fileprivate func createCancelImage() { + guard cancelImage == nil else { return } + let cancelImage = MTCancelView(target: self, action: #selector(clear)) + cancelImage.frame = CGRect( + x: frame.size.width - 55, y: (frame.size.height - 45) / 2, width: 45, height: 45) + addSubview(cancelImage) + self.cancelImage = cancelImage + } + + @objc fileprivate func tap(_ tap: MTTapGestureRecognizer) { + handleTap(at: tap.location(in: self)) + } + + fileprivate func handleTap(at point: CGPoint) { + if !isFirstResponderCompat { + insertionIndex = nil + caretView.showHandle(false) + startEditing() + return + } + + insertionIndex = + closestIndex(to: point) ?? MTMathListIndex.level0Index(UInt(mathList.atoms.count)) + caretView.showHandle(true) + insertionPointChanged() + } + + fileprivate static func clearPlaceholders(in mathList: MTMathList?) { + guard let mathList else { return } + for atom in mathList.atoms { + if atom.type == .placeholder { + atom.nucleus = MTSymbolWhiteSquare + } + if atom.superScript != nil { + clearPlaceholders(in: atom.superScript) + } + if atom.subScript != nil { + clearPlaceholders(in: atom.subScript) + } + if atom.type == .radical, let radical = atom as? MTRadical { + clearPlaceholders(in: radical.degree) + clearPlaceholders(in: radical.radicand) + } + if atom.type == .fraction, let fraction = atom as? MTFraction { + clearPlaceholders(in: fraction.numerator) + clearPlaceholders(in: fraction.denominator) + } + } + } + + fileprivate func insertionPointChanged() { + guard isFirstResponderCompat else { + caretView.removeFromSuperview() + cancelImage?.isHidden = true + return + } + + Self.clearPlaceholders(in: mathList) + + if let index = insertionIndex, let atom = mathList.atom(atListIndex: index), + atom.type == .placeholder + { + atom.nucleus = MTSymbolBlackSquare + if index.finalSubIndexType() == .subIndexTypeNucleus { + insertionIndex = index.levelDown() + } + } else if let previousIndex = insertionIndex?.previous(), + let atom = mathList.atom(atListIndex: previousIndex), + atom.type == .placeholder, + atom.superScript == nil, + atom.subScript == nil + { + insertionIndex = previousIndex + atom.nucleus = MTSymbolBlackSquare + } + + setKeyboardMode() + + guard let insertionIndex else { return } + let caretPosition = caretRect(for: insertionIndex) + guard caretPosition != CGPoint(x: -1, y: -1) else { return } + + caretView.setPosition(caretPosition.applying(flipTransform)) + if caretView.superview == nil { + addSubview(caretView) + setNeedsDisplayCompat() + } + + cancelImage?.isHidden = false + caretView.delayBlink() + setLabelNeedsLayoutCompat() + } + + fileprivate func setKeyboardMode() { + setKeyboardValue(false, forKey: "exponentHighlighted") + setKeyboardValue(false, forKey: "radicalHighlighted") + setKeyboardValue(false, forKey: "squareRootHighlighted") + + if insertionIndex?.hasSubIndex(of: .subIndexTypeSuperscript) == true { + setKeyboardValue(true, forKey: "exponentHighlighted") + setKeyboardValue(false, forKey: "equalsAllowed") + } + if insertionIndex?.subIndexType == .subIndexTypeNumerator { + setKeyboardValue(false, forKey: "equalsAllowed") + } + if insertionIndex?.subIndexType == .subIndexTypeDegree { + setKeyboardValue(true, forKey: "radicalHighlighted") + } else if insertionIndex?.subIndexType == .subIndexTypeRadicand { + setKeyboardValue(true, forKey: "squareRootHighlighted") + } + } + + fileprivate func atom(forCharacter scalar: UnicodeScalar) -> MTMathAtom? { + let string = String(scalar) + if let atom = MTMathAtomFactory.atom(forCharacter: UInt16(scalar.value)) { + return atom + } + switch string { + case MTSymbolMultiplication: + return MTMathAtomFactory.times() + case MTSymbolSquareRoot: + return MTMathAtomFactory.placeholderSquareRoot() + case MTSymbolInfinity, MTSymbolDegree, MTSymbolAngle: + return MTMathAtom(type: .ordinary, value: string) + case MTSymbolDivision: + return MTMathAtomFactory.divide() + case MTSymbolFractionSlash: + return MTMathAtomFactory.placeholderFraction() + case "{": + return MTMathAtom(type: .open, value: string) + case "}": + return MTMathAtom(type: .close, value: string) + case MTSymbolGreaterEqual, MTSymbolLessEqual: + return MTMathAtom(type: .relation, value: string) + case "*": + return MTMathAtomFactory.times() + case "/": + return MTMathAtomFactory.divide() + default: + break + } + + let value = scalar.value + if (greekLowerStart...greekLowerEnd).contains(value) + || (greekCapitalStart...greekCapitalEnd).contains(value) + { + return MTMathAtom(type: .variable, value: string) + } + if value < 0x21 || value > 0x7E || string == "'" || string == "~" { + return nil + } + return MTMathAtom(type: .ordinary, value: string) + } + + fileprivate func handleExponentButton() { + handleScriptButton(.subIndexTypeSuperscript) + } + + fileprivate func handleSubscriptButton() { + handleScriptButton(.subIndexTypeSubscript) + } + + fileprivate func handleScriptButton(_ type: MTMathListSubIndexType) { + guard let insertionIndex else { return } + if insertionIndex.hasSubIndex(of: type) { + self.insertionIndex = getIndexAfterSpecialStructure(insertionIndex, type: type) + return + } + + if !insertionIndex.isAtBeginningOfLine(), + let atom = mathList.atom(atListIndex: insertionIndex.previous()) + { + let hadScript = scriptList(for: atom, type: type) != nil + let count = ensureScriptList(for: atom, type: type).atoms.count + if !hadScript { + self.insertionIndex = insertionIndex.previous()?.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), type: type) + } else if insertionIndex.finalSubIndexType() == .subIndexTypeNucleus { + self.insertionIndex = insertionIndex.levelDown()?.levelUp( + withSubIndex: MTMathListIndex.level0Index(UInt(count)), type: type) + } else { + self.insertionIndex = insertionIndex.previous()?.levelUp( + withSubIndex: MTMathListIndex.level0Index(UInt(count)), type: type) + } + return + } + + let emptyAtom = MTMathAtomFactory.placeholder() + setScriptList(makePlaceholderMathList(), on: emptyAtom, type: type) + if !updatePlaceholderIfPresent(emptyAtom) { + mathList.insert(emptyAtom, atListIndex: insertionIndex) + } + self.insertionIndex = insertionIndex.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), type: type) + } + + fileprivate func getIndexAfterSpecialStructure( + _ index: MTMathListIndex, type: MTMathListSubIndexType + ) -> MTMathListIndex { + var nextIndex = index + while nextIndex.hasSubIndex(of: type) { + nextIndex = nextIndex.levelDown() ?? nextIndex + } + return nextIndex.next() + } + + fileprivate func handleSlashButton() { + guard let insertionIndex else { return } + let numerator = MTMathList() + var current = insertionIndex + while !current.isAtBeginningOfLine() { + guard let atom = mathList.atom(atListIndex: current.previous()) else { break } + if atom.type != .number && atom.type != .variable { + break + } + numerator.insert(atom, atListIndex: MTMathListIndex.level0Index(0)) + current = current.previous()! + } + + if current.atomIndex == insertionIndex.atomIndex { + if let atom = atom(forCharacter: "1".unicodeScalars.first!) { + numerator.addAtom(atom) + } + if !current.isAtBeginningOfLine(), + let previousAtom = mathList.atom(atListIndex: current.previous()), + previousAtom.type == .fraction + { + let times = MTMathAtomFactory.times() + mathList.insert(times, atListIndex: current) + current = current.next() + } + } else { + mathList.removeAtoms( + inListIndexRange: MTMathListRange.make( + current, length: insertionIndex.atomIndex - current.atomIndex)) + } + + let fraction = MTFraction() + fraction.denominator = MTMathList() + fraction.denominator.addAtom(MTMathAtomFactory.placeholder()) + fraction.numerator = numerator + mathList.insert(fraction, atListIndex: current) + self.insertionIndex = current.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDenominator) + } + + fileprivate func getOutOfRadical(_ index: MTMathListIndex) -> MTMathListIndex { + var index = index + if index.hasSubIndex(of: .subIndexTypeDegree) { + index = getIndexAfterSpecialStructure(index, type: .subIndexTypeDegree) + } + if index.hasSubIndex(of: .subIndexTypeRadicand) { + index = getIndexAfterSpecialStructure(index, type: .subIndexTypeRadicand) + } + return index + } + + fileprivate func handleRadical(withDegreeButtonPressed: Bool) { + guard let current = insertionIndex else { return } + + if current.hasSubIndex(of: .subIndexTypeDegree) + || current.hasSubIndex(of: .subIndexTypeRadicand), + let radical = mathList.atoms[Int(current.atomIndex)] as? MTRadical + { + if withDegreeButtonPressed { + if radical.degree == nil { + radical.degree = MTMathList() + radical.degree?.addAtom(MTMathAtomFactory.placeholder()) + insertionIndex = current.levelDown()?.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDegree) + } else if current.hasSubIndex(of: .subIndexTypeRadicand) { + insertionIndex = current.levelDown()?.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDegree) + } else { + insertionIndex = getOutOfRadical(current) + } + } else if current.hasSubIndex(of: .subIndexTypeDegree) { + insertionIndex = current.levelDown()?.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeRadicand) + } else { + insertionIndex = getOutOfRadical(current) + } + return + } + + let radical = + withDegreeButtonPressed + ? MTMathAtomFactory.placeholderRadical() : MTMathAtomFactory.placeholderSquareRoot() + mathList.insert(radical, atListIndex: current) + insertionIndex = current.levelUp( + withSubIndex: MTMathListIndex.level0Index(0), + type: withDegreeButtonPressed ? .subIndexTypeDegree : .subIndexTypeRadicand + ) + } + + fileprivate func removePlaceholderIfPresent() { + guard let insertionIndex, let current = mathList.atom(atListIndex: insertionIndex), + current.type == .placeholder + else { return } + mathList.removeAtom(atListIndex: insertionIndex) + } + + fileprivate func updatePlaceholderIfPresent(_ atom: MTMathAtom) -> Bool { + guard let insertionIndex, + let current = mathList.atom(atListIndex: insertionIndex), + current.type == .placeholder + else { return false } + if let superScript = current.superScript { + atom.superScript = superScript + } + if let subScript = current.subScript { + atom.subScript = subScript + } + mathList.removeAtom(atListIndex: insertionIndex) + mathList.insert(atom, atListIndex: insertionIndex) + return true + } + + fileprivate func isTrigFunction(_ string: String) -> Bool { + ["sin", "cos", "tan", "sec", "csc", "cot"].contains(string) + } + + fileprivate func insertParens() { + insertPairedAtoms(open: "(", close: ")") + } + + fileprivate func insertAbsValue() { + insertPairedAtoms(open: "|", close: "|") + } + + fileprivate func insertPairedAtoms(open: Character, close: Character) { + guard let openAtom = atom(forCharacter: open.unicodeScalars.first!), + let closeAtom = atom(forCharacter: close.unicodeScalars.first!), + let insertionIndex + else { return } + mathList.insert(openAtom, atListIndex: insertionIndex) + self.insertionIndex = insertionIndex.next() + if let insertionIndex = self.insertionIndex { + mathList.insert(closeAtom, atListIndex: insertionIndex) + } + } + + fileprivate func makePlaceholderMathList() -> MTMathList { + let list = MTMathList() + list.addAtom(MTMathAtomFactory.placeholder()) + return list + } + + fileprivate func scriptList(for atom: MTMathAtom, type: MTMathListSubIndexType) -> MTMathList? { + switch type { + case .subIndexTypeSuperscript: + atom.superScript + case .subIndexTypeSubscript: + atom.subScript + default: + nil + } + } + + fileprivate func setScriptList( + _ list: MTMathList?, on atom: MTMathAtom, type: MTMathListSubIndexType + ) { + switch type { + case .subIndexTypeSuperscript: + atom.superScript = list + case .subIndexTypeSubscript: + atom.subScript = list + default: + break + } + } + + @discardableResult + fileprivate func ensureScriptList(for atom: MTMathAtom, type: MTMathListSubIndexType) + -> MTMathList + { + if let list = scriptList(for: atom, type: type) { + return list + } + let list = makePlaceholderMathList() + setScriptList(list, on: atom, type: type) + return list + } + + fileprivate func performSelector( + _ selectorName: String, on object: AnyObject?, with argument: AnyObject + ) { + let selector = NSSelectorFromString(selectorName) + guard let object = object as? NSObject, object.responds(to: selector) else { return } + object.perform(selector, with: argument) + } + + fileprivate func setKeyboardValue(_ value: Bool, forKey key: String) { + (keyboard as? NSObject)?.setValue(value, forKey: key) + } + + fileprivate func layoutLabelIfNeeded() { + #if canImport(UIKit) + label.layoutIfNeeded() + #else + label.layoutSubtreeIfNeeded() + #endif + } + + fileprivate func setNeedsDisplayCompat() { + #if canImport(UIKit) + setNeedsDisplay() + #else + needsDisplay = true + #endif + } + + fileprivate func setNeedsLayoutCompat() { + #if canImport(UIKit) + setNeedsLayout() + #else + needsLayout = true + #endif + } + + fileprivate func setLabelNeedsLayoutCompat() { + #if canImport(UIKit) + label.setNeedsLayout() + #else + label.needsLayout = true + #endif + } + + fileprivate var isFirstResponderCompat: Bool { + #if canImport(UIKit) + isFirstResponder + #else + window?.firstResponder === self + #endif + } +} + +#if canImport(UIKit) + extension MTEditableMathLabelSwift: UIKeyInput { + public override var canBecomeFirstResponder: Bool { true } + + public override var inputView: UIView? { + keyboard as? UIView + } + + public override func becomeFirstResponder() -> Bool { + let didBecome = super.becomeFirstResponder() + if didBecome { + doBecomeFirstResponder() + } + return didBecome + } + + public override func resignFirstResponder() -> Bool { + guard isFirstResponder else { return true } + let didResign = super.resignFirstResponder() + doResignFirstResponder() + return didResign + } + + public override func layoutSubviews() { + super.layoutSubviews() + doLayout() + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if super.point(inside: point, with: event) { + return true + } + return caretView.point(inside: convert(point, to: caretView), with: event) + } + } +#else + extension MTEditableMathLabelSwift { + public override var acceptsFirstResponder: Bool { true } + + public override func becomeFirstResponder() -> Bool { + let didBecome = super.becomeFirstResponder() + if didBecome { + doBecomeFirstResponder() + } + return didBecome + } + + public override func resignFirstResponder() -> Bool { + guard window?.firstResponder === self else { return true } + let didResign = super.resignFirstResponder() + doResignFirstResponder() + return didResign + } + + public override func keyDown(with event: NSEvent) { + interpretKeyEvents([event]) + } + + public override func deleteBackward(_ sender: Any?) { + deleteBackward() + } + + public override func layout() { + super.layout() + doLayout() + } + + public override var isFlipped: Bool { true } + + public override func hitTest(_ point: NSPoint) -> NSView? { + hitTestOutsideBounds(point, ignoringSubviews: [label]) + } + } +#endif From 3c64ec4472ee03527abca5b944d476ed3d0b4bca Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 22:38:04 +0000 Subject: [PATCH 097/133] more improvements --- .../MTEditableMathLabelSwift.swift | 85 ++++++++++++++----- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/mathEditorSwift/MTEditableMathLabelSwift.swift b/mathEditorSwift/MTEditableMathLabelSwift.swift index dbacd56..3e68bf3 100644 --- a/mathEditorSwift/MTEditableMathLabelSwift.swift +++ b/mathEditorSwift/MTEditableMathLabelSwift.swift @@ -12,8 +12,44 @@ private let greekLowerEnd: UInt32 = 0x03C9 private let greekCapitalStart: UInt32 = 0x0391 private let greekCapitalEnd: UInt32 = 0x03A9 +public protocol MTEditableMathLabelSwiftDelegate: AnyObject { + func returnPressed(_ label: MTEditableMathLabelSwift) + func textModified(_ label: MTEditableMathLabelSwift) + func didBeginEditing(_ label: MTEditableMathLabelSwift) + func didEndEditing(_ label: MTEditableMathLabelSwift) +} + +public extension MTEditableMathLabelSwiftDelegate { + func returnPressed(_ label: MTEditableMathLabelSwift) {} + func textModified(_ label: MTEditableMathLabelSwift) {} + func didBeginEditing(_ label: MTEditableMathLabelSwift) {} + func didEndEditing(_ label: MTEditableMathLabelSwift) {} +} + +public protocol MTMathKeyboardTraitsSwift: AnyObject { + var equalsAllowed: Bool { get set } + var fractionsAllowed: Bool { get set } + var variablesAllowed: Bool { get set } + var numbersAllowed: Bool { get set } + var operatorsAllowed: Bool { get set } + var exponentHighlighted: Bool { get set } + var squareRootHighlighted: Bool { get set } + var radicalHighlighted: Bool { get set } +} + +public protocol MTKeyInputSwift: AnyObject { + func insertText(_ text: String) + func deleteBackward() + var hasText: Bool { get } +} + +public protocol MTMathKeyboardSwift: MTMathKeyboardTraitsSwift { + func startedEditing(_ label: MTView & MTKeyInputSwift) + func finishedEditing(_ label: MTView & MTKeyInputSwift) +} + @objc(MTEditableMathLabelSwift) -public final class MTEditableMathLabelSwift: MTView { +public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { @objc public var mathList: MTMathList = MTMathList() { didSet { label.mathList = mathList @@ -36,8 +72,8 @@ public final class MTEditableMathLabelSwift: MTView { @objc public private(set) var cancelImage: MTCancelView? @objc public private(set) var caretView: MTCaretViewSwift! - @objc public weak var delegate: NSObjectProtocol? - @objc public weak var keyboard: MTView? + public weak var delegate: MTEditableMathLabelSwiftDelegate? + public weak var keyboard: (MTView & MTMathKeyboardSwift)? @objc public var fontSize: CGFloat { get { label.fontSize } @@ -141,21 +177,21 @@ public final class MTEditableMathLabelSwift: MTView { if insertionIndex == nil { insertionIndex = MTMathListIndex.level0Index(UInt(mathList.atoms.count)) } - performSelector("startedEditing:", on: keyboard, with: self) + keyboard?.startedEditing(self) insertionPointChanged() - performSelector("didBeginEditing:", on: delegate, with: self) + delegate?.didBeginEditing(self) } @objc public func doResignFirstResponder() { - performSelector("finishedEditing:", on: keyboard, with: self) + keyboard?.finishedEditing(self) insertionPointChanged() - performSelector("didEndEditing:", on: delegate, with: self) + delegate?.didEndEditing(self) } @objc(insertText:) public func insertText(_ string: String) { if string == "\n" { - performSelector("returnPressed:", on: delegate, with: self) + delegate?.returnPressed(self) return } @@ -211,7 +247,7 @@ public final class MTEditableMathLabelSwift: MTView { insertParens() } - performSelector("textModified:", on: delegate, with: self) + delegate?.textModified(self) } @objc public func deleteBackward() { @@ -243,7 +279,7 @@ public final class MTEditableMathLabelSwift: MTView { label.mathList = mathList insertionPointChanged() - performSelector("textModified:", on: delegate, with: self) + delegate?.textModified(self) } @objc public var hasText: Bool { @@ -677,16 +713,27 @@ extension MTEditableMathLabelSwift { return list } - fileprivate func performSelector( - _ selectorName: String, on object: AnyObject?, with argument: AnyObject - ) { - let selector = NSSelectorFromString(selectorName) - guard let object = object as? NSObject, object.responds(to: selector) else { return } - object.perform(selector, with: argument) - } - fileprivate func setKeyboardValue(_ value: Bool, forKey key: String) { - (keyboard as? NSObject)?.setValue(value, forKey: key) + switch key { + case "equalsAllowed": + keyboard?.equalsAllowed = value + case "fractionsAllowed": + keyboard?.fractionsAllowed = value + case "variablesAllowed": + keyboard?.variablesAllowed = value + case "numbersAllowed": + keyboard?.numbersAllowed = value + case "operatorsAllowed": + keyboard?.operatorsAllowed = value + case "exponentHighlighted": + keyboard?.exponentHighlighted = value + case "squareRootHighlighted": + keyboard?.squareRootHighlighted = value + case "radicalHighlighted": + keyboard?.radicalHighlighted = value + default: + break + } } fileprivate func layoutLabelIfNeeded() { From 512f79102212057b98f80f16b688e17d84a155af Mon Sep 17 00:00:00 2001 From: Madiyar Date: Tue, 24 Mar 2026 22:47:28 +0000 Subject: [PATCH 098/133] Small improvements --- .../MTEditableMathLabelSwift+NSView.swift | 18 ++ .../MTEditableMathLabelSwift+Responder.swift | 57 ++++ ...MTEditableMathLabelSwift+UITextInput.swift | 264 ++++++++++++++++++ .../MTEditableMathLabelSwift+UIView.swift | 19 ++ .../MTEditableMathLabelSwift.swift | 107 +------ 5 files changed, 364 insertions(+), 101 deletions(-) create mode 100644 mathEditorSwift/MTEditableMathLabelSwift+NSView.swift create mode 100644 mathEditorSwift/MTEditableMathLabelSwift+Responder.swift create mode 100644 mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift create mode 100644 mathEditorSwift/MTEditableMathLabelSwift+UIView.swift diff --git a/mathEditorSwift/MTEditableMathLabelSwift+NSView.swift b/mathEditorSwift/MTEditableMathLabelSwift+NSView.swift new file mode 100644 index 0000000..ddf00c3 --- /dev/null +++ b/mathEditorSwift/MTEditableMathLabelSwift+NSView.swift @@ -0,0 +1,18 @@ +import Foundation + +#if canImport(AppKit) + import AppKit + + extension MTEditableMathLabelSwift { + public override func layout() { + super.layout() + doLayout() + } + + public override var isFlipped: Bool { true } + + public override func hitTest(_ point: NSPoint) -> NSView? { + hitTestOutsideBounds(point, ignoringSubviews: [label]) + } + } +#endif diff --git a/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift b/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift new file mode 100644 index 0000000..646486b --- /dev/null +++ b/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift @@ -0,0 +1,57 @@ +import Foundation + +#if canImport(UIKit) + import UIKit + + extension MTEditableMathLabelSwift: UIKeyInput { + public override var inputView: UIView? { + keyboard as? UIView + } + + public override var canBecomeFirstResponder: Bool { true } + + public override func becomeFirstResponder() -> Bool { + let didBecome = super.becomeFirstResponder() + if didBecome { + doBecomeFirstResponder() + } + return didBecome + } + + public override func resignFirstResponder() -> Bool { + guard isFirstResponder else { return true } + let didResign = super.resignFirstResponder() + doResignFirstResponder() + return didResign + } + } +#elseif canImport(AppKit) + import AppKit + + extension MTEditableMathLabelSwift { + public override var acceptsFirstResponder: Bool { true } + + public override func becomeFirstResponder() -> Bool { + let didBecome = super.becomeFirstResponder() + if didBecome { + doBecomeFirstResponder() + } + return didBecome + } + + public override func resignFirstResponder() -> Bool { + guard window?.firstResponder === self else { return true } + let didResign = super.resignFirstResponder() + doResignFirstResponder() + return didResign + } + + public override func keyDown(with event: NSEvent) { + interpretKeyEvents([event]) + } + + public override func deleteBackward(_ sender: Any?) { + deleteBackward() + } + } +#endif diff --git a/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift b/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift new file mode 100644 index 0000000..da8792f --- /dev/null +++ b/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift @@ -0,0 +1,264 @@ +import Foundation + +#if canImport(UIKit) + import ObjectiveC + import UIKit + + private final class MTTextInputPosition: UITextPosition {} + + private final class MTTextInputRange: UITextRange { + private let internalStart = MTTextInputPosition() + private let internalEnd = MTTextInputPosition() + + override var start: UITextPosition { internalStart } + override var end: UITextPosition { internalEnd } + override var isEmpty: Bool { true } + } + + private final class WeakTextInputDelegateBox { + weak var value: UITextInputDelegate? + + init(_ value: UITextInputDelegate?) { + self.value = value + } + } + + private enum MTTextInputAssociatedKeys { + static var selectedTextRange = "mt_selectedTextRange" + static var inputDelegate = "mt_inputDelegate" + static var markedTextRange = "mt_markedTextRange" + static var markedTextStyle = "mt_markedTextStyle" + static var tokenizer = "mt_tokenizer" + static var beginningOfDocument = "mt_beginningOfDocument" + static var endOfDocument = "mt_endOfDocument" + } + + extension MTEditableMathLabelSwift: UITextInput { + public var selectedTextRange: UITextRange? { + get { + objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.selectedTextRange) as? UITextRange + } + set { + objc_setAssociatedObject( + self, + &MTTextInputAssociatedKeys.selectedTextRange, + newValue, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + } + } + + public var inputDelegate: UITextInputDelegate? { + get { + ( + objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.inputDelegate) + as? WeakTextInputDelegateBox + )?.value + } + set { + objc_setAssociatedObject( + self, + &MTTextInputAssociatedKeys.inputDelegate, + WeakTextInputDelegateBox(newValue), + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + } + } + + public var markedTextRange: UITextRange? { + objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.markedTextRange) as? UITextRange + } + + public var markedTextStyle: [NSAttributedString.Key: Any]? { + get { + objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.markedTextStyle) + as? [NSAttributedString.Key: Any] + } + set { + objc_setAssociatedObject( + self, + &MTTextInputAssociatedKeys.markedTextStyle, + newValue, + .OBJC_ASSOCIATION_COPY_NONATOMIC + ) + } + } + + public var beginningOfDocument: UITextPosition { + if let position = objc_getAssociatedObject( + self, + &MTTextInputAssociatedKeys.beginningOfDocument + ) as? UITextPosition { + return position + } + let position = MTTextInputPosition() + objc_setAssociatedObject( + self, + &MTTextInputAssociatedKeys.beginningOfDocument, + position, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + return position + } + + public var endOfDocument: UITextPosition { + if let position = objc_getAssociatedObject( + self, + &MTTextInputAssociatedKeys.endOfDocument + ) as? UITextPosition { + return position + } + let position = MTTextInputPosition() + objc_setAssociatedObject( + self, + &MTTextInputAssociatedKeys.endOfDocument, + position, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + return position + } + + public var tokenizer: UITextInputTokenizer { + if let tokenizer = objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.tokenizer) + as? UITextInputTokenizer + { + return tokenizer + } + let tokenizer = UITextInputStringTokenizer(textInput: self) + objc_setAssociatedObject( + self, + &MTTextInputAssociatedKeys.tokenizer, + tokenizer, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + return tokenizer + } + + public func baseWritingDirection( + for position: UITextPosition, + in direction: UITextStorageDirection + ) -> NSWritingDirection { + .leftToRight + } + + public func caretRect(for position: UITextPosition) -> CGRect { + .zero + } + + public func unmarkText() {} + + public func characterRange(at point: CGPoint) -> UITextRange? { + nil + } + + public func characterRange( + byExtending position: UITextPosition, + in direction: UITextLayoutDirection + ) -> UITextRange? { + nil + } + + public func closestPosition(to point: CGPoint) -> UITextPosition? { + nil + } + + public func closestPosition(to point: CGPoint, within range: UITextRange) -> UITextPosition? { + nil + } + + public func compare(_ position: UITextPosition, to other: UITextPosition) -> ComparisonResult { + .orderedSame + } + + public func dictationRecognitionFailed() {} + + public func dictationRecordingDidEnd() {} + + public func firstRect(for range: UITextRange) -> CGRect { + .zero + } + + public func frame( + forDictationResultPlaceholder placeholder: Any + ) -> CGRect { + .zero + } + + public func insertDictationResult(_ dictationResult: [UIDictationPhrase]) {} + + public func insertDictationResultPlaceholder() -> Any { + MTTextInputRange() + } + + public func offset(from: UITextPosition, to toPosition: UITextPosition) -> Int { + 0 + } + + public func position( + from position: UITextPosition, + in direction: UITextLayoutDirection, + offset: Int + ) -> UITextPosition? { + nil + } + + public func position(from position: UITextPosition, offset: Int) -> UITextPosition? { + nil + } + + public func position( + within range: UITextRange, + farthestIn direction: UITextLayoutDirection + ) -> UITextPosition? { + nil + } + + public func removeDictationResultPlaceholder( + _ placeholder: Any, + willInsertResult: Bool + ) {} + + public func replace(_ range: UITextRange, withText text: String) {} + + public func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { + [] + } + + public func setBaseWritingDirection( + _ writingDirection: NSWritingDirection, + for range: UITextRange + ) {} + + public func setMarkedText(_ markedText: String?, selectedRange: NSRange) {} + + public func text(in range: UITextRange) -> String? { + nil + } + + public func textRange(from fromPosition: UITextPosition, to toPosition: UITextPosition) + -> UITextRange? + { + nil + } + + public var autocapitalizationType: UITextAutocapitalizationType { + .none + } + + public var autocorrectionType: UITextAutocorrectionType { + .no + } + + public var returnKeyType: UIReturnKeyType { + .default + } + + public var spellCheckingType: UITextSpellCheckingType { + .no + } + + public var keyboardType: UIKeyboardType { + .asciiCapable + } + } +#endif diff --git a/mathEditorSwift/MTEditableMathLabelSwift+UIView.swift b/mathEditorSwift/MTEditableMathLabelSwift+UIView.swift new file mode 100644 index 0000000..961660c --- /dev/null +++ b/mathEditorSwift/MTEditableMathLabelSwift+UIView.swift @@ -0,0 +1,19 @@ +import Foundation + +#if canImport(UIKit) + import UIKit + + extension MTEditableMathLabelSwift { + public override func layoutSubviews() { + super.layoutSubviews() + doLayout() + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if super.point(inside: point, with: event) { + return true + } + return caretView.point(inside: convert(point, to: caretView), with: event) + } + } +#endif diff --git a/mathEditorSwift/MTEditableMathLabelSwift.swift b/mathEditorSwift/MTEditableMathLabelSwift.swift index 3e68bf3..7eaa1f8 100644 --- a/mathEditorSwift/MTEditableMathLabelSwift.swift +++ b/mathEditorSwift/MTEditableMathLabelSwift.swift @@ -89,7 +89,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { set { label.contentInsets = newValue } } - private let label = MTMathUILabel(frame: .zero) + let label = MTMathUILabel(frame: .zero) private var tapGestureRecognizer: MTTapGestureRecognizer? private var insertionIndex: MTMathListIndex? private var flipTransform = CGAffineTransform.identity @@ -124,7 +124,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { } @objc public func clearHighlights() { - setNeedsLayoutCompat() + setNeedsLayout() } @objc(moveCaretToPoint:) @@ -134,7 +134,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { } @objc public func startEditing() { - guard !isFirstResponderCompat else { return } + guard !isFirstResponder else { return } #if canImport(AppKit) window?.makeFirstResponder(self) #else @@ -343,7 +343,7 @@ extension MTEditableMathLabelSwift { } fileprivate func handleTap(at point: CGPoint) { - if !isFirstResponderCompat { + if !isFirstResponder { insertionIndex = nil caretView.showHandle(false) startEditing() @@ -380,7 +380,7 @@ extension MTEditableMathLabelSwift { } fileprivate func insertionPointChanged() { - guard isFirstResponderCompat else { + guard isFirstResponder else { caretView.removeFromSuperview() cancelImage?.isHidden = true return @@ -752,102 +752,7 @@ extension MTEditableMathLabelSwift { #endif } - fileprivate func setNeedsLayoutCompat() { - #if canImport(UIKit) - setNeedsLayout() - #else - needsLayout = true - #endif - } - fileprivate func setLabelNeedsLayoutCompat() { - #if canImport(UIKit) - label.setNeedsLayout() - #else - label.needsLayout = true - #endif - } - - fileprivate var isFirstResponderCompat: Bool { - #if canImport(UIKit) - isFirstResponder - #else - window?.firstResponder === self - #endif + label.setNeedsLayout() } } - -#if canImport(UIKit) - extension MTEditableMathLabelSwift: UIKeyInput { - public override var canBecomeFirstResponder: Bool { true } - - public override var inputView: UIView? { - keyboard as? UIView - } - - public override func becomeFirstResponder() -> Bool { - let didBecome = super.becomeFirstResponder() - if didBecome { - doBecomeFirstResponder() - } - return didBecome - } - - public override func resignFirstResponder() -> Bool { - guard isFirstResponder else { return true } - let didResign = super.resignFirstResponder() - doResignFirstResponder() - return didResign - } - - public override func layoutSubviews() { - super.layoutSubviews() - doLayout() - } - - public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - if super.point(inside: point, with: event) { - return true - } - return caretView.point(inside: convert(point, to: caretView), with: event) - } - } -#else - extension MTEditableMathLabelSwift { - public override var acceptsFirstResponder: Bool { true } - - public override func becomeFirstResponder() -> Bool { - let didBecome = super.becomeFirstResponder() - if didBecome { - doBecomeFirstResponder() - } - return didBecome - } - - public override func resignFirstResponder() -> Bool { - guard window?.firstResponder === self else { return true } - let didResign = super.resignFirstResponder() - doResignFirstResponder() - return didResign - } - - public override func keyDown(with event: NSEvent) { - interpretKeyEvents([event]) - } - - public override func deleteBackward(_ sender: Any?) { - deleteBackward() - } - - public override func layout() { - super.layout() - doLayout() - } - - public override var isFlipped: Bool { true } - - public override func hitTest(_ point: NSPoint) -> NSView? { - hitTestOutsideBounds(point, ignoringSubviews: [label]) - } - } -#endif From 58ea7cdff2b8d754aa929a2b9113f5bb6496db46 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Wed, 25 Mar 2026 23:27:26 +0000 Subject: [PATCH 099/133] Bring back the commnets --- .../Internal/MTCaretViewSwift.swift | 8 +++ .../MTEditableMathLabelSwift+Responder.swift | 2 + ...MTEditableMathLabelSwift+UITextInput.swift | 8 +-- .../MTEditableMathLabelSwift.swift | 71 +++++++++++++++++-- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/mathEditorSwift/Internal/MTCaretViewSwift.swift b/mathEditorSwift/Internal/MTCaretViewSwift.swift index e2cc261..6c3e348 100644 --- a/mathEditorSwift/Internal/MTCaretViewSwift.swift +++ b/mathEditorSwift/Internal/MTCaretViewSwift.swift @@ -17,6 +17,7 @@ private let caretHandleDescent: CGFloat = 8 private let caretHandleHeight: CGFloat = 20 private let caretHandleHitAreaSize: CGFloat = 44 +// The settings below make sense for the given font size. They are scaled appropriately when the fontsize changes. private func caretHeight() -> CGFloat { caretAscent + caretDescent } @@ -70,10 +71,12 @@ private final class MTCaretHandleSwift: MTView { let caretPoint = CGPoint(x: localPoint.x, y: localPoint.y - frame.origin.y) guard let label else { return } let labelPoint = convert(caretPoint, to: label) + // puts the point at the top to the top of the current caret label.moveCaret(to: labelPoint) } private func hitArea() -> CGRect { + // Create a hit area around the center. let size = bounds.size return CGRect( x: (size.width - caretHandleHitAreaSize) / 2, @@ -103,6 +106,7 @@ private final class MTCaretHandleSwift: MTView { } public override func touchesMoved(_ touches: Set, with event: UIEvent?) { + // From apple documentation guard let touch = touches.first else { return } handleDrag(localPoint: touch.location(in: self)) } @@ -198,6 +202,7 @@ public final class MTCaretViewSwift: MTView { } func setPosition(_ position: CGPoint) { + // position is in the parent's coordinate system and it is the bottom left corner of the view. frame = CGRect(x: position.x, y: position.y - caretAscent * scale, width: 0, height: 0) } @@ -210,12 +215,14 @@ public final class MTCaretViewSwift: MTView { handle.isHidden = !show } + // Helper method to set an initial blink delay func delayBlink() { isHidden = false blinker.isHidden = false blinkTimer?.fireDate = Date(timeIntervalSinceNow: initialBlinkDelay) } + // Helper method to toggle hidden state of caret view. private func blink() { blinker.isHidden.toggle() } @@ -252,6 +259,7 @@ public final class MTCaretViewSwift: MTView { #if canImport(UIKit) public override func didMoveToSuperview() { super.didMoveToSuperview() + // UIView didMoveToSuperview override to set up blink timers after caret view created in superview. isHidden = false startBlinkingIfNeeded() } diff --git a/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift b/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift index 646486b..abc8554 100644 --- a/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift +++ b/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift @@ -47,6 +47,8 @@ import Foundation } public override func keyDown(with event: NSEvent) { + // interpretKeyEvents feeds the event into the input system, + // which calls insertText: or deleteBackward: as appropriate. interpretKeyEvents([event]) } diff --git a/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift b/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift index da8792f..751f717 100644 --- a/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift +++ b/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift @@ -4,6 +4,8 @@ import Foundation import ObjectiveC import UIKit + // These are blank just to get a UITextInput implementation, to fix the dictation button bug. + // Proposed fix from: http://stackoverflow.com/questions/20980898/work-around-for-dictation-custom-text-view-bug private final class MTTextInputPosition: UITextPosition {} private final class MTTextInputRange: UITextRange { @@ -50,10 +52,8 @@ import Foundation public var inputDelegate: UITextInputDelegate? { get { - ( - objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.inputDelegate) - as? WeakTextInputDelegateBox - )?.value + (objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.inputDelegate) + as? WeakTextInputDelegateBox)?.value } set { objc_setAssociatedObject( diff --git a/mathEditorSwift/MTEditableMathLabelSwift.swift b/mathEditorSwift/MTEditableMathLabelSwift.swift index 7eaa1f8..d16a19c 100644 --- a/mathEditorSwift/MTEditableMathLabelSwift.swift +++ b/mathEditorSwift/MTEditableMathLabelSwift.swift @@ -12,6 +12,7 @@ private let greekLowerEnd: UInt32 = 0x03C9 private let greekCapitalStart: UInt32 = 0x0391 private let greekCapitalEnd: UInt32 = 0x03A9 +/// Delegate for the `MTEditableMathLabel`. All methods are optional. public protocol MTEditableMathLabelSwiftDelegate: AnyObject { func returnPressed(_ label: MTEditableMathLabelSwift) func textModified(_ label: MTEditableMathLabelSwift) @@ -19,13 +20,16 @@ public protocol MTEditableMathLabelSwiftDelegate: AnyObject { func didEndEditing(_ label: MTEditableMathLabelSwift) } -public extension MTEditableMathLabelSwiftDelegate { - func returnPressed(_ label: MTEditableMathLabelSwift) {} - func textModified(_ label: MTEditableMathLabelSwift) {} - func didBeginEditing(_ label: MTEditableMathLabelSwift) {} - func didEndEditing(_ label: MTEditableMathLabelSwift) {} +extension MTEditableMathLabelSwiftDelegate { + public func returnPressed(_ label: MTEditableMathLabelSwift) {} + public func textModified(_ label: MTEditableMathLabelSwift) {} + public func didBeginEditing(_ label: MTEditableMathLabelSwift) {} + public func didEndEditing(_ label: MTEditableMathLabelSwift) {} } +/// This protocol provides information on the context of the current insertion point. +/// The keyboard may choose to enable/disable/highlight certain parts of the UI depending on the context. +/// e.g. you cannot enter the = sign when you are in a fraction so the keyboard could disable that. public protocol MTMathKeyboardTraitsSwift: AnyObject { var equalsAllowed: Bool { get set } var fractionsAllowed: Bool { get set } @@ -43,6 +47,13 @@ public protocol MTKeyInputSwift: AnyObject { var hasText: Bool { get } } +/// Any keyboard that provides input to the `MTEditableMathUILabel` must implement +/// this protocol. +/// +/// This protocol informs the keyboard when a particular `MTEditableMathUILabel` is being edited. +/// The keyboard should use this information to send `MTKeyInput` messages to the label. +/// +/// This protocol inherits from `MTMathKeyboardTraits`. public protocol MTMathKeyboardSwift: MTMathKeyboardTraitsSwift { func startedEditing(_ label: MTView & MTKeyInputSwift) func finishedEditing(_ label: MTView & MTKeyInputSwift) @@ -150,6 +161,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { @objc(insertMathList:atPoint:) public func insertMathList(_ list: MTMathList, at point: CGPoint) { guard let detailedIndex = closestIndex(to: point) else { return } + // insert at the given index - but don't consider sublevels at this point var index = MTMathListIndex.level0Index(detailedIndex.atomIndex) for atom in list.atoms { mathList.insert(atom, atListIndex: index) @@ -167,6 +179,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { @objc public func doLayout() { cancelImage?.frame = CGRect( x: frame.size.width - 55, y: (frame.size.height - 45) / 2, width: 45, height: 45) + // update the flip transform let transform = CGAffineTransform(translationX: 0, y: bounds.size.height) flipTransform = CGAffineTransform(scaleX: 1, y: -1).concatenating(transform) layoutLabelIfNeeded() @@ -200,6 +213,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { var insertedAtom: MTMathAtom? if string.count > 1 { + // Check if this is a supported command insertedAtom = MTMathAtomFactory.atom(forLatexSymbolName: string) } else { insertedAtom = atom(forCharacter: scalar) @@ -229,9 +243,11 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { default: if let insertedAtom, let insertionIndex { if !updatePlaceholderIfPresent(insertedAtom) { + // If a placeholder wasn't updated then insert the new element. mathList.insert(insertedAtom, atListIndex: insertionIndex) } if insertedAtom.type == .fraction { + // go to the numerator self.insertionIndex = insertionIndex.levelUp( withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeNumerator) } else { @@ -243,6 +259,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { label.mathList = mathList insertionPointChanged() + // If trig function, insert parens after if isTrigFunction(string) { insertParens() } @@ -253,6 +270,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { @objc public func deleteBackward() { guard hasText, var previousIndex = insertionIndex?.previous() else { return } + // delete the last atom from the list mathList.removeAtom(atListIndex: previousIndex) if previousIndex.finalSubIndexType() == MTMathListSubIndexType.subIndexTypeNucleus, let downIndex = previousIndex.levelDown() @@ -270,8 +288,11 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { if insertionIndex?.isAtBeginningOfLine() == true, insertionIndex?.subIndexType != .subIndexTypeNone { + // We have deleted to the beginning of the line and it is not the outermost line if mathList.atom(atListIndex: insertionIndex) == nil, let insertionIndex { + // add a placeholder if we deleted everything in the list let atom = MTMathAtomFactory.placeholder() + // mark the placeholder as selected since that is the current insertion point. atom.nucleus = MTSymbolBlackSquare mathList.insert(atom, atListIndex: insertionIndex) } @@ -289,6 +310,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { @objc(closestIndexToPoint:) public func closestIndex(to point: CGPoint) -> MTMathListIndex? { layoutLabelIfNeeded() + // no mathlist, so can't figure it out. guard let displayList = label.displayList else { return nil } return displayList.closestIndex(to: convert(point, to: label)) } @@ -296,6 +318,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { @objc(caretRectForIndex:) public func caretRect(for index: MTMathListIndex) -> CGPoint { layoutLabelIfNeeded() + // no mathlist so we can't figure it out. guard let displayList = label.displayList else { return .zero } return displayList.caretPosition(for: index) } @@ -303,10 +326,12 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { extension MTEditableMathLabelSwift { fileprivate func initialize() { + // Add tap gesture recognizer to let the user enter editing mode. let tap = MTTapGestureRecognizer(target: self, action: #selector(tap(_:))) addGestureRecognizer(tap) tapGestureRecognizer = tap + // Create and set up the APLSimpleCoreTextView that will do the drawing. addSubview(label) label.pinToSuperview() label.fontSize = 30 @@ -326,6 +351,7 @@ extension MTEditableMathLabelSwift { highlightColor = MTColor.systemRed bringSubviewToFront(cancelImage!) + // start with an empty math list mathList = MTMathList() } @@ -350,6 +376,7 @@ extension MTEditableMathLabelSwift { return } + // If already editing move the cursor and show handle insertionIndex = closestIndex(to: point) ?? MTMathListIndex.level0Index(UInt(mathList.atoms.count)) caretView.showHandle(true) @@ -379,7 +406,9 @@ extension MTEditableMathLabelSwift { } } + // Helper method to update caretView when insertion point/selection changes. fileprivate func insertionPointChanged() { + // If not in editing mode, we don't show the caret. guard isFirstResponder else { caretView.removeFromSuperview() cancelImage?.isHidden = true @@ -393,6 +422,7 @@ extension MTEditableMathLabelSwift { { atom.nucleus = MTSymbolBlackSquare if index.finalSubIndexType() == .subIndexTypeNucleus { + // If the insertion index is inside a placeholder, move it out. insertionIndex = index.levelDown() } } else if let previousIndex = insertionIndex?.previous(), @@ -407,17 +437,22 @@ extension MTEditableMathLabelSwift { setKeyboardMode() + // Find the insert point rect and create a caretView to draw the caret at this position. guard let insertionIndex else { return } let caretPosition = caretRect(for: insertionIndex) + // Check tht we were returned a valid position before displaying a caret there. guard caretPosition != CGPoint(x: -1, y: -1) else { return } + // caretFrame is in the flipped coordinate system, flip it back caretView.setPosition(caretPosition.applying(flipTransform)) if caretView.superview == nil { addSubview(caretView) setNeedsDisplayCompat() } + // when a caret is displayed, the X symbol should be as well cancelImage?.isHidden = false + // Set up a timer to "blink" the caret. caretView.delayBlink() setLabelNeedsLayoutCompat() } @@ -443,6 +478,8 @@ extension MTEditableMathLabelSwift { fileprivate func atom(forCharacter scalar: UnicodeScalar) -> MTMathAtom? { let string = String(scalar) + // Get the basic conversion from MTMathAtomFactory, and then special case + // unicode characters and latex special characters. if let atom = MTMathAtomFactory.atom(forCharacter: UInt16(scalar.value)) { return atom } @@ -475,11 +512,14 @@ extension MTEditableMathLabelSwift { if (greekLowerStart...greekLowerEnd).contains(value) || (greekCapitalStart...greekCapitalEnd).contains(value) { + // All greek chars are rendered as variables. return MTMathAtom(type: .variable, value: string) } if value < 0x21 || value > 0x7E || string == "'" || string == "~" { + // not ascii return nil } + // just an ordinary character return MTMathAtom(type: .ordinary, value: string) } @@ -494,6 +534,7 @@ extension MTEditableMathLabelSwift { fileprivate func handleScriptButton(_ type: MTMathListSubIndexType) { guard let insertionIndex else { return } if insertionIndex.hasSubIndex(of: type) { + // The index is currently inside a script. The button gets it out of the script and move forward. self.insertionIndex = getIndexAfterSpecialStructure(insertionIndex, type: type) return } @@ -507,6 +548,7 @@ extension MTEditableMathLabelSwift { self.insertionIndex = insertionIndex.previous()?.levelUp( withSubIndex: MTMathListIndex.level0Index(0), type: type) } else if insertionIndex.finalSubIndexType() == .subIndexTypeNucleus { + // If we are already inside the nucleus, then we come out and go up to the script self.insertionIndex = insertionIndex.levelDown()?.levelUp( withSubIndex: MTMathListIndex.level0Index(UInt(count)), type: type) } else { @@ -519,6 +561,7 @@ extension MTEditableMathLabelSwift { let emptyAtom = MTMathAtomFactory.placeholder() setScriptList(makePlaceholderMathList(), on: emptyAtom, type: type) if !updatePlaceholderIfPresent(emptyAtom) { + // If the placeholder hasn't been updated then insert it. mathList.insert(emptyAtom, atListIndex: insertionIndex) } self.insertionIndex = insertionIndex.levelUp( @@ -532,23 +575,28 @@ extension MTEditableMathLabelSwift { while nextIndex.hasSubIndex(of: type) { nextIndex = nextIndex.levelDown() ?? nextIndex } + //Point to just after this node. return nextIndex.next() } fileprivate func handleSlashButton() { guard let insertionIndex else { return } + // special / handling - makes the thing a fraction let numerator = MTMathList() var current = insertionIndex while !current.isAtBeginningOfLine() { guard let atom = mathList.atom(atListIndex: current.previous()) else { break } if atom.type != .number && atom.type != .variable { + // we don't put this atom on the fraction break } + // add the number to the beginning of the list numerator.insert(atom, atListIndex: MTMathListIndex.level0Index(0)) current = current.previous()! } if current.atomIndex == insertionIndex.atomIndex { + // so we didn't really find any numbers before this, so make the numerator 1 if let atom = atom(forCharacter: "1".unicodeScalars.first!) { numerator.addAtom(atom) } @@ -557,10 +605,12 @@ extension MTEditableMathLabelSwift { previousAtom.type == .fraction { let times = MTMathAtomFactory.times() + // add a times symbol mathList.insert(times, atListIndex: current) current = current.next() } } else { + // delete stuff in the mathlist from current to _insertionIndex mathList.removeAtoms( inListIndexRange: MTMathListRange.make( current, length: insertionIndex.atomIndex - current.atomIndex)) @@ -570,7 +620,9 @@ extension MTEditableMathLabelSwift { fraction.denominator = MTMathList() fraction.denominator.addAtom(MTMathAtomFactory.placeholder()) fraction.numerator = numerator + // insert it mathList.insert(fraction, atListIndex: current) + // update the insertion index to go the denominator self.insertionIndex = current.levelUp( withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDenominator) } @@ -600,15 +652,19 @@ extension MTEditableMathLabelSwift { insertionIndex = current.levelDown()?.levelUp( withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDegree) } else if current.hasSubIndex(of: .subIndexTypeRadicand) { + // If the cursor is at the radicand, switch it to the degree insertionIndex = current.levelDown()?.levelUp( withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDegree) } else { + // If the cursor is at the degree, get out of the radical insertionIndex = getOutOfRadical(current) } } else if current.hasSubIndex(of: .subIndexTypeDegree) { + // If the radical the cursor at has a degree, and the cursor is at the degree, move the cursor to the radicand. insertionIndex = current.levelDown()?.levelUp( withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeRadicand) } else { + // If the cursor is at the radicand, get out of the radical. insertionIndex = getOutOfRadical(current) } return @@ -628,9 +684,11 @@ extension MTEditableMathLabelSwift { guard let insertionIndex, let current = mathList.atom(atListIndex: insertionIndex), current.type == .placeholder else { return } + // remove this element - the inserted text replaces the placeholder mathList.removeAtom(atListIndex: insertionIndex) } + // Returns true if updated fileprivate func updatePlaceholderIfPresent(_ atom: MTMathAtom) -> Bool { guard let insertionIndex, let current = mathList.atom(atListIndex: insertionIndex), @@ -642,11 +700,13 @@ extension MTEditableMathLabelSwift { if let subScript = current.subScript { atom.subScript = subScript } + // remove the placeholder and replace with atom. mathList.removeAtom(atListIndex: insertionIndex) mathList.insert(atom, atListIndex: insertionIndex) return true } + // Return YES if string is a trig function, otherwise return NO fileprivate func isTrigFunction(_ string: String) -> Bool { ["sin", "cos", "tan", "sec", "csc", "cot"].contains(string) } @@ -669,6 +729,7 @@ extension MTEditableMathLabelSwift { if let insertionIndex = self.insertionIndex { mathList.insert(closeAtom, atListIndex: insertionIndex) } + // Don't go to the next insertion index, to start inserting before the close parens. } fileprivate func makePlaceholderMathList() -> MTMathList { From 145b9a53e0a27ea47276bc96f7b669df68d1cd5d Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 26 Mar 2026 00:43:40 +0000 Subject: [PATCH 100/133] regressions.md --- regressions.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 regressions.md diff --git a/regressions.md b/regressions.md new file mode 100644 index 0000000..e194293 --- /dev/null +++ b/regressions.md @@ -0,0 +1,59 @@ +# MTEditableMathLabelSwift regressions vs legacy ObjC + +## Confirmed from prior review + +### P2: Redraw the math label after applying a highlight + +- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L129) +- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L835) + +`highlightCharacter(at:)` mutates `label.displayList`, but the Swift port invalidates the wrapper view instead of the embedded `MTMathUILabel` that actually renders the expression. In the ObjC implementation, `highlightCharacterAtIndex:` calls `[self.label setNeedsDisplay]`. In Swift, the redraw can be missed until some unrelated update forces the label to repaint. + +### P2: Clear highlights by relayouting the inner label, not the wrapper + +- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L137) +- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L849) + +The highlight state lives in the label display list. The ObjC implementation clears it by calling `[self.label setNeedsLayout]`, forcing the embedded math label to rebuild its layout/display list. The Swift port calls `setNeedsLayout()` on the wrapper instead, which can leave stale highlights visible. + +## Additional regressions found by comparing ObjC and Swift + +### P1: `insertMathList(_:at:)` becomes a no-op when there is no display list + +- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L161) +- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L369) + +The Swift implementation does `guard let detailedIndex = closestIndex(to: point) else { return }`. That means inserting into an empty label, or a label whose display list has not been built yet, silently does nothing. The ObjC version still inserts at top-level index `0`, because `detailedIndex.atomIndex` on `nil` collapses to `0` under Objective-C nil messaging. This is a functional regression for insertion into empty/not-yet-laid-out editors. + +### P1: Swift dropped nib/storyboard initialization support + +- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L113) +- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L36) + +The legacy ObjC control supports both programmatic initialization and archive-based initialization via `awakeFromNib`. The Swift port marks `init(coder:)` unavailable and unconditionally traps. Any existing XIB, storyboard, or archived view usage that worked with the ObjC implementation cannot be migrated to the Swift class without breaking at runtime. + +### P2: Editing taps now show the caret handle instead of hiding it + +- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L379) +- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L216) + +When the user taps while already editing, the ObjC implementation moves the insertion point and hides the caret handle with `showHandle:NO`. The Swift port changed this to `showHandle(true)`. That changes visible behavior and also expands the active touch target after every tap because hit testing includes the caret view. + +### P1: Swift dropped Objective-C-visible `delegate` and `keyboard` integration points + +- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L86) +- ObjC reference: [mathEditor/editor/MTEditableMathLabel.h](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.h#L77) + +The legacy control exposes `delegate` and `keyboard` as public Objective-C properties backed by Objective-C protocols, so existing ObjC hosts can assign delegates, provide custom keyboards, and receive editing callbacks. In the Swift rewrite, both properties are Swift-only: the properties themselves are not marked `@objc`, and their protocol types are not Objective-C protocols. That means mixed ObjC code cannot wire up these integration points to the Swift class, which is a migration blocker for existing legacy consumers. + +### P2: `textColor = nil` no longer clears the embedded label color + +- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L74) +- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L112) + +The ObjC setter forwards the assigned value directly to `self.label.textColor`, so callers can reset the math label to its default rendering by assigning `nil`. The Swift setter coalesces `nil` back to the current value with `newValue ?? label.textColor`, so clearing the color becomes a no-op. Any code that relied on clearing a previously customized text color now behaves differently. + +## Notes + +- The legacy ObjC implementation does not have the two highlight invalidation regressions. It correctly targets `self.label` in both `highlightCharacterAtIndex:` and `clearHighlights`. +- Keyboard-state logic in `setKeyboardMode()` appears materially equivalent between ObjC and Swift. From a4cfcf8e82fef61cc475e68246858c7bb20d698a Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 26 Mar 2026 10:41:10 +0000 Subject: [PATCH 101/133] Append verified highlight and API parity regressions --- regressions.md | 165 +++++++++++++++++++++++++++++++------------------ 1 file changed, 106 insertions(+), 59 deletions(-) diff --git a/regressions.md b/regressions.md index e194293..2620783 100644 --- a/regressions.md +++ b/regressions.md @@ -1,59 +1,106 @@ -# MTEditableMathLabelSwift regressions vs legacy ObjC - -## Confirmed from prior review - -### P2: Redraw the math label after applying a highlight - -- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L129) -- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L835) - -`highlightCharacter(at:)` mutates `label.displayList`, but the Swift port invalidates the wrapper view instead of the embedded `MTMathUILabel` that actually renders the expression. In the ObjC implementation, `highlightCharacterAtIndex:` calls `[self.label setNeedsDisplay]`. In Swift, the redraw can be missed until some unrelated update forces the label to repaint. - -### P2: Clear highlights by relayouting the inner label, not the wrapper - -- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L137) -- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L849) - -The highlight state lives in the label display list. The ObjC implementation clears it by calling `[self.label setNeedsLayout]`, forcing the embedded math label to rebuild its layout/display list. The Swift port calls `setNeedsLayout()` on the wrapper instead, which can leave stale highlights visible. - -## Additional regressions found by comparing ObjC and Swift - -### P1: `insertMathList(_:at:)` becomes a no-op when there is no display list - -- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L161) -- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L369) - -The Swift implementation does `guard let detailedIndex = closestIndex(to: point) else { return }`. That means inserting into an empty label, or a label whose display list has not been built yet, silently does nothing. The ObjC version still inserts at top-level index `0`, because `detailedIndex.atomIndex` on `nil` collapses to `0` under Objective-C nil messaging. This is a functional regression for insertion into empty/not-yet-laid-out editors. - -### P1: Swift dropped nib/storyboard initialization support - -- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L113) -- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L36) - -The legacy ObjC control supports both programmatic initialization and archive-based initialization via `awakeFromNib`. The Swift port marks `init(coder:)` unavailable and unconditionally traps. Any existing XIB, storyboard, or archived view usage that worked with the ObjC implementation cannot be migrated to the Swift class without breaking at runtime. - -### P2: Editing taps now show the caret handle instead of hiding it - -- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L379) -- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L216) - -When the user taps while already editing, the ObjC implementation moves the insertion point and hides the caret handle with `showHandle:NO`. The Swift port changed this to `showHandle(true)`. That changes visible behavior and also expands the active touch target after every tap because hit testing includes the caret view. - -### P1: Swift dropped Objective-C-visible `delegate` and `keyboard` integration points - -- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L86) -- ObjC reference: [mathEditor/editor/MTEditableMathLabel.h](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.h#L77) - -The legacy control exposes `delegate` and `keyboard` as public Objective-C properties backed by Objective-C protocols, so existing ObjC hosts can assign delegates, provide custom keyboards, and receive editing callbacks. In the Swift rewrite, both properties are Swift-only: the properties themselves are not marked `@objc`, and their protocol types are not Objective-C protocols. That means mixed ObjC code cannot wire up these integration points to the Swift class, which is a migration blocker for existing legacy consumers. - -### P2: `textColor = nil` no longer clears the embedded label color - -- Swift: [mathEditorSwift/MTEditableMathLabelSwift.swift](/Users/madiyar/Dev/iOS/MathEditor/mathEditorSwift/MTEditableMathLabelSwift.swift#L74) -- ObjC reference: [mathEditor/editor/MTEditableMathLabel.m](/Users/madiyar/Dev/iOS/MathEditor/mathEditor/editor/MTEditableMathLabel.m#L112) - -The ObjC setter forwards the assigned value directly to `self.label.textColor`, so callers can reset the math label to its default rendering by assigning `nil`. The Swift setter coalesces `nil` back to the current value with `newValue ?? label.textColor`, so clearing the color becomes a no-op. Any code that relied on clearing a previously customized text color now behaves differently. - -## Notes - -- The legacy ObjC implementation does not have the two highlight invalidation regressions. It correctly targets `self.label` in both `highlightCharacterAtIndex:` and `clearHighlights`. -- Keyboard-state logic in `setKeyboardMode()` appears materially equivalent between ObjC and Swift. +# Swift vs Objective-C regression review + +## Scope +Compared `mathEditorSwift` against legacy `mathEditor`, with emphasis on Swift-only `guard` and early-return behavior. + +## Regressions + +### 1) `insertMathList(_:at:)` silently drops inserts when no closest index is available +- **Swift behavior**: Returns early when `closestIndex(to:)` is `nil`. + - `guard let detailedIndex = closestIndex(to: point) else { return }` +- **Legacy Objective-C behavior**: No early return; code proceeds using `detailedIndex.atomIndex` even when `detailedIndex` is `nil` (Objective-C nil messaging effectively falls back to index `0`), so list content still gets inserted. +- **Impact**: Programmatic inserts can be lost in Swift in edge cases where the display list is not yet ready. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 2) Swift drops normal typed input when `insertionIndex` is `nil` +- **Swift behavior**: In `insertText(_:)`, normal atom insertion is gated by `if let insertedAtom, let insertionIndex { ... }`; when `insertionIndex` is `nil`, nothing is inserted and no fallback index is chosen. +- **Legacy Objective-C behavior**: Uses `_insertionIndex` directly; with Objective-C nil messaging, insertion APIs are still invoked rather than skipped. +- **Impact**: Input can be ignored in transient states (e.g., before insertion point is initialized) instead of being inserted at a default position. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 3) Swift special insert operations no-op when `insertionIndex` is `nil` +- **Swift behavior**: Multiple operations immediately return on missing `insertionIndex`: + - `handleScriptButton(_:)` + - `handleSlashButton()` + - `handleRadical(withDegreeButtonPressed:)` + - `insertPairedAtoms(open:close:)` +- **Legacy Objective-C behavior**: Same operations execute without explicit index guards; nil messaging allows the methods to proceed instead of immediate no-op. +- **Impact**: `^`, `_`, `/`, radicals, and paired insertions (`()`, `||`) can be dropped in Swift under the same transient nil-index state. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +## Guard-specific note requested +The Swift pattern `guard let insertionIndex else { return }` (and similar early returns) is the main source of these regressions. In Objective-C, equivalent paths commonly continued because messaging `nil` did not short-circuit the entire operation. + +## Additional regressions (added) + +### 4) Paired insert shortcuts (`"()"`, `"||"`) can be dropped entirely by Swift early return +- **Swift behavior**: `insertText(_:)` routes to `insertParens()` / `insertAbsValue()`, which both call `insertPairedAtoms(open:close:)`. That helper has `guard let insertionIndex ... else { return }`, so the whole shortcut no-ops when the insertion index is temporarily nil. +- **Legacy Objective-C behavior**: `insertParens`/`insertAbsValue` directly execute insertions with `_insertionIndex` and do not short-circuit on a nil guard. +- **Impact**: User-visible shortcuts can be ignored in transient index-init windows where legacy behavior still inserted delimiters. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 5) First-key race after tap-to-edit: Swift can drop operators due guard-based nil-index exits +- **Swift behavior**: `handleTap(at:)` sets `insertionIndex = nil` before `startEditing()`. If a key arrives before `doBecomeFirstResponder()` reinitializes the index, operations like `^`, `_`, `/`, radicals, and paired insertions can return early through Swift guards. +- **Legacy Objective-C behavior**: The same tap path nils `_insertionIndex`, but operation handlers do not use `guard` exits and continue through nil-messaging paths. +- **Impact**: The first key after entering edit mode can be intermittently dropped in Swift under timing-sensitive input sequences. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +## Additional regressions (highlighting / API parity) + +### 6) Highlight redraw targets wrapper view instead of `MTMathUILabel` +- **Swift behavior**: `highlightCharacter(at:)` updates `displayList` and calls `setNeedsDisplayCompat()` on the wrapper view. +- **Legacy Objective-C behavior**: `highlightCharacterAtIndex:` calls `[self.label setNeedsDisplay]` on the embedded math label. +- **Impact**: Highlight changes can be visually delayed/missed until some other update repaints `MTMathUILabel`. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 7) Clearing highlights relayouts wrapper instead of the inner math label +- **Swift behavior**: `clearHighlights()` calls `setNeedsLayout()` on the wrapper view. +- **Legacy Objective-C behavior**: `clearHighlights` calls `[self.label setNeedsLayout]`, directly invalidating the display-list owner. +- **Impact**: Stale highlight state may persist because the label display list is not explicitly rebuilt. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 8) Swift class cannot be initialized from nib/storyboard (`init(coder:)` unavailable) +- **Swift behavior**: `init(coder:)` is marked unavailable and traps. +- **Legacy Objective-C behavior**: Supports archive-based initialization via `awakeFromNib`. +- **Impact**: Existing nib/storyboard integrations that worked with `MTEditableMathLabel` are not drop-in compatible with `MTEditableMathLabelSwift`. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 9) Tap-while-editing now shows the caret handle (legacy hid it) +- **Swift behavior**: In `handleTap(at:)`, already-editing taps call `caretView.showHandle(true)`. +- **Legacy Objective-C behavior**: `handleTapAtPoint:` uses `[_caretView showHandle:NO]` in the same branch. +- **Impact**: Visible interaction behavior changes and touch-hit area around caret becomes active after each editing tap. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 10) `textColor = nil` no longer clears custom text color +- **Swift behavior**: Setter coalesces nil with `newValue ?? label.textColor`, turning nil assignment into a no-op. +- **Legacy Objective-C behavior**: Setter forwards `textColor` directly to `self.label.textColor`, allowing nil reset semantics. +- **Impact**: Hosts that clear a previously set color by assigning nil no longer get legacy behavior. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.m` + +### 11) Objective-C integration points for `delegate`/`keyboard` are not API-compatible +- **Swift behavior**: `delegate` and `keyboard` are Swift-only protocol-typed properties (not exposed with Objective-C protocol-compatible types). +- **Legacy Objective-C behavior**: Public ObjC properties use ObjC protocols (`id`, `MTView*`). +- **Impact**: Legacy ObjC hosts cannot wire the same delegate/keyboard contracts to `MTEditableMathLabelSwift` as a drop-in migration. +- **Relevant files**: + - `mathEditorSwift/MTEditableMathLabelSwift.swift` + - `mathEditor/editor/MTEditableMathLabel.h` From f595e4a5eb5ece734df560465b7bb7b6064487b9 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 26 Mar 2026 12:44:36 +0000 Subject: [PATCH 102/133] Reorganize MathEditorSwift package sources layout --- Package.swift | 13 +++-------- mathEditorSwift/Package.swift | 23 +++++++++++++++++++ .../Internal/MTCancelView.swift | 0 .../Internal/MTCaretViewSwift.swift | 0 .../Internal/MTDisplay+Editing.swift | 0 .../Internal/MTMathList+Editing.swift | 0 .../Internal/MTTapGestureRecognizer.swift | 0 .../Internal/MTView/MTView+AutoLayout.swift | 0 .../Internal/MTView/MTView.swift | 0 .../MTView/NSView+FirstResponder.swift | 0 .../Internal/MTView/NSView+HitTest.swift | 0 .../Internal/MTView/NSView+Layout.swift | 0 .../MTEditableMathLabelSwift+NSView.swift | 0 .../MTEditableMathLabelSwift+Responder.swift | 0 ...MTEditableMathLabelSwift+UITextInput.swift | 0 .../MTEditableMathLabelSwift+UIView.swift | 0 .../MTEditableMathLabelSwift.swift | 0 17 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 mathEditorSwift/Package.swift rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTCancelView.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTCaretViewSwift.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTDisplay+Editing.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTMathList+Editing.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTTapGestureRecognizer.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTView/MTView+AutoLayout.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTView/MTView.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTView/NSView+FirstResponder.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTView/NSView+HitTest.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/Internal/MTView/NSView+Layout.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/MTEditableMathLabelSwift+NSView.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/MTEditableMathLabelSwift+Responder.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/MTEditableMathLabelSwift+UITextInput.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/MTEditableMathLabelSwift+UIView.swift (100%) rename mathEditorSwift/{ => Sources/MathEditorSwift}/MTEditableMathLabelSwift.swift (100%) diff --git a/Package.swift b/Package.swift index 0039460..759c9a2 100644 --- a/Package.swift +++ b/Package.swift @@ -10,31 +10,24 @@ let package = Package( .library( name: "MathEditor", targets: ["MathEditor"]), - .library( - name: "MathEditorSwift", - targets: ["MathEditorSwift"]), .library( name: "MathKeyboard", targets: ["MathKeyboard"]), ], dependencies: [ - .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") + .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master"), + .package(path: "./mathEditorSwift") ], targets: [ .target( name: "MathEditor", - dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditorSwift"], + dependencies: [.product(name: "iosMath", package: "iosMath"), .product(name: "MathEditorSwift", package: "MathEditorSwift")], path: "./mathEditor", cSettings: [ .headerSearchPath("./editor"), .headerSearchPath("./internal"), ] ), - .target( - name: "MathEditorSwift", - dependencies: [.product(name: "iosMath", package: "iosMath")], - path: "./mathEditorSwift", - ), .target( name: "MathKeyboard", dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditor"], diff --git a/mathEditorSwift/Package.swift b/mathEditorSwift/Package.swift new file mode 100644 index 0000000..6b210a4 --- /dev/null +++ b/mathEditorSwift/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version: 5.6 + +import PackageDescription + +let package = Package( + name: "MathEditorSwift", + platforms: [.iOS(.v13), .macOS(.v11)], + products: [ + .library( + name: "MathEditorSwift", + targets: ["MathEditorSwift"] + ) + ], + dependencies: [ + .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") + ], + targets: [ + .target( + name: "MathEditorSwift", + dependencies: [.product(name: "iosMath", package: "iosMath")] + ) + ] +) diff --git a/mathEditorSwift/Internal/MTCancelView.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift similarity index 100% rename from mathEditorSwift/Internal/MTCancelView.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift diff --git a/mathEditorSwift/Internal/MTCaretViewSwift.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift similarity index 100% rename from mathEditorSwift/Internal/MTCaretViewSwift.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift diff --git a/mathEditorSwift/Internal/MTDisplay+Editing.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift similarity index 100% rename from mathEditorSwift/Internal/MTDisplay+Editing.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift diff --git a/mathEditorSwift/Internal/MTMathList+Editing.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift similarity index 100% rename from mathEditorSwift/Internal/MTMathList+Editing.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift diff --git a/mathEditorSwift/Internal/MTTapGestureRecognizer.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift similarity index 100% rename from mathEditorSwift/Internal/MTTapGestureRecognizer.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift diff --git a/mathEditorSwift/Internal/MTView/MTView+AutoLayout.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift similarity index 100% rename from mathEditorSwift/Internal/MTView/MTView+AutoLayout.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift diff --git a/mathEditorSwift/Internal/MTView/MTView.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView.swift similarity index 100% rename from mathEditorSwift/Internal/MTView/MTView.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView.swift diff --git a/mathEditorSwift/Internal/MTView/NSView+FirstResponder.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift similarity index 100% rename from mathEditorSwift/Internal/MTView/NSView+FirstResponder.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift diff --git a/mathEditorSwift/Internal/MTView/NSView+HitTest.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift similarity index 100% rename from mathEditorSwift/Internal/MTView/NSView+HitTest.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift diff --git a/mathEditorSwift/Internal/MTView/NSView+Layout.swift b/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift similarity index 100% rename from mathEditorSwift/Internal/MTView/NSView+Layout.swift rename to mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift diff --git a/mathEditorSwift/MTEditableMathLabelSwift+NSView.swift b/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift similarity index 100% rename from mathEditorSwift/MTEditableMathLabelSwift+NSView.swift rename to mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift diff --git a/mathEditorSwift/MTEditableMathLabelSwift+Responder.swift b/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift similarity index 100% rename from mathEditorSwift/MTEditableMathLabelSwift+Responder.swift rename to mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift diff --git a/mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift b/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift similarity index 100% rename from mathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift rename to mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift diff --git a/mathEditorSwift/MTEditableMathLabelSwift+UIView.swift b/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift similarity index 100% rename from mathEditorSwift/MTEditableMathLabelSwift+UIView.swift rename to mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift diff --git a/mathEditorSwift/MTEditableMathLabelSwift.swift b/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift similarity index 100% rename from mathEditorSwift/MTEditableMathLabelSwift.swift rename to mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift From e7c240fd6e45401d041fc3041ed0d18f08e52080 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 12:55:46 +0000 Subject: [PATCH 103/133] Rename to MathEditorSwift --- {mathEditorSwift => MathEditorSwift}/Package.swift | 0 .../Sources/MathEditorSwift/Internal/MTCancelView.swift | 0 .../Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift | 0 .../Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift | 0 .../Sources/MathEditorSwift/Internal/MTMathList+Editing.swift | 0 .../Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift | 0 .../MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift | 0 .../Sources/MathEditorSwift/Internal/MTView/MTView.swift | 0 .../MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift | 0 .../Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift | 0 .../Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift | 0 .../Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift | 0 .../MathEditorSwift/MTEditableMathLabelSwift+Responder.swift | 0 .../MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift | 0 .../Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift | 0 .../Sources/MathEditorSwift/MTEditableMathLabelSwift.swift | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename {mathEditorSwift => MathEditorSwift}/Package.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTCancelView.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTView/MTView.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift (100%) rename {mathEditorSwift => MathEditorSwift}/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift (100%) diff --git a/mathEditorSwift/Package.swift b/MathEditorSwift/Package.swift similarity index 100% rename from mathEditorSwift/Package.swift rename to MathEditorSwift/Package.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift rename to MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift rename to MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift rename to MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift rename to MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift diff --git a/mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift similarity index 100% rename from mathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift rename to MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift From dadfaed7828df78945789f3eeeee4d3ae60a4f7c Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 13:00:24 +0000 Subject: [PATCH 104/133] Update --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 759c9a2..60cff5c 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master"), - .package(path: "./mathEditorSwift") + .package(path: "./MathEditorSwift") ], targets: [ .target( From a11415268681db590f93207a5203b04ff51be817 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 13:02:59 +0000 Subject: [PATCH 105/133] iosMath simpliufy --- MathEditorSwift/Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MathEditorSwift/Package.swift b/MathEditorSwift/Package.swift index 6b210a4..0d925d3 100644 --- a/MathEditorSwift/Package.swift +++ b/MathEditorSwift/Package.swift @@ -17,7 +17,7 @@ let package = Package( targets: [ .target( name: "MathEditorSwift", - dependencies: [.product(name: "iosMath", package: "iosMath")] + dependencies: ["iosMath"] ) ] ) From 26067afd777971862af7930a4b13f94d2d19ebe9 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 13:04:51 +0000 Subject: [PATCH 106/133] Fix --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 60cff5c..974cc5b 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( targets: [ .target( name: "MathEditor", - dependencies: [.product(name: "iosMath", package: "iosMath"), .product(name: "MathEditorSwift", package: "MathEditorSwift")], + dependencies: ["iosMath", "MathEditorSwift"], path: "./mathEditor", cSettings: [ .headerSearchPath("./editor"), @@ -30,7 +30,7 @@ let package = Package( ), .target( name: "MathKeyboard", - dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditor"], + dependencies: ["iosMath", "MathEditor"], path: "./mathKeyboard", resources: [.process("MathKeyboardResources")], cSettings: [ From e8b089048927db5174dca4b0494f9941fccfdbdb Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 13:09:37 +0000 Subject: [PATCH 107/133] Add To Do --- todo.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 todo.md diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..93b0f9b --- /dev/null +++ b/todo.md @@ -0,0 +1,28 @@ + +## MTCaretView + +1. - [ ] No need for baseColor vs color +2. - [ ] setNeedsDisplayCompatk +3. - [ ] handleDrag convert is different implementation +4. - [ ] no mouse cancelled +5. - [ ] can caretColor be non-optional +6. - [ ] Can caret handle initialized with label +7. - [ ] is startBlinkingIfNeeded correct? +8. - [ ] Rename MTCaretHandleSwift to CaretHandle + +## MTEditableMathlabelSwift + +1. - [ ] MTKeyInputSwift should be alias to UIKeyInput +2. - [x] Bring comments back +3. - [ ] highlightColor is different in `initialize` +4. - [ ] layoutLabelIfNeeded should be the same as objc +5. - [ ] setNeedsDisplayCompat should be changed +6. - [ ] setLabelNeedsDisplayCompat should be changed +7. - [ ] getIndexAfterSpecialStructure: is next should unwrapped? + +### Responder + +1. - [ ] Should not extend UIKeyInput +2. - [ ] reuse becomeFirstResponder on both +3. - [ ] Reuse resignFirstResponder on both +4. - [ ] From 3ecc741ce0eb8a3985d492149c997f2ebaa70e70 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 13:16:10 +0000 Subject: [PATCH 108/133] Revert mathEditor --- .../editor/MTEditableMathLabel+NSView.m | 3 +- .../editor/MTEditableMathLabel+Responder.m | 3 +- mathEditor/editor/MTEditableMathLabel.m | 11 +- mathEditor/internal/MTCancelView.h | 13 + mathEditor/internal/MTCancelView.m | 50 ++ mathEditor/internal/MTCaretView.m | 4 +- mathEditor/internal/MTDisplay+Editing.h | 29 + mathEditor/internal/MTDisplay+Editing.m | 596 ++++++++++++++++++ mathEditor/internal/MTMathList+Editing.h | 26 + mathEditor/internal/MTMathList+Editing.m | 286 +++++++++ .../internal/MTView/MTView+AutoLayout.h | 20 + .../internal/MTView/MTView+AutoLayout.m | 32 + .../internal/MTView/MTView+FirstResponder.h | 22 + .../internal/MTView/MTView+FirstResponder.m | 26 + mathEditor/internal/MTView/MTView+HitTest.h | 23 + mathEditor/internal/MTView/MTView+HitTest.m | 46 ++ mathEditor/internal/MTView/MTView+Layout.h | 28 + mathEditor/internal/MTView/MTView+Layout.m | 40 ++ mathEditor/internal/MTView/MXView.h | 14 + 19 files changed, 1263 insertions(+), 9 deletions(-) create mode 100644 mathEditor/internal/MTCancelView.h create mode 100644 mathEditor/internal/MTCancelView.m create mode 100644 mathEditor/internal/MTDisplay+Editing.h create mode 100644 mathEditor/internal/MTDisplay+Editing.m create mode 100644 mathEditor/internal/MTMathList+Editing.h create mode 100644 mathEditor/internal/MTMathList+Editing.m create mode 100644 mathEditor/internal/MTView/MTView+AutoLayout.h create mode 100644 mathEditor/internal/MTView/MTView+AutoLayout.m create mode 100644 mathEditor/internal/MTView/MTView+FirstResponder.h create mode 100644 mathEditor/internal/MTView/MTView+FirstResponder.m create mode 100644 mathEditor/internal/MTView/MTView+HitTest.h create mode 100644 mathEditor/internal/MTView/MTView+HitTest.m create mode 100644 mathEditor/internal/MTView/MTView+Layout.h create mode 100644 mathEditor/internal/MTView/MTView+Layout.m create mode 100644 mathEditor/internal/MTView/MXView.h diff --git a/mathEditor/editor/MTEditableMathLabel+NSView.m b/mathEditor/editor/MTEditableMathLabel+NSView.m index 95252e5..9bfa91e 100644 --- a/mathEditor/editor/MTEditableMathLabel+NSView.m +++ b/mathEditor/editor/MTEditableMathLabel+NSView.m @@ -9,8 +9,7 @@ #import "MTEditableMathLabel.h" #import "MTMathUILabel.h" - -@import MathEditorSwift; +#import "MTView/MTView+HitTest.h" NS_ASSUME_NONNULL_BEGIN diff --git a/mathEditor/editor/MTEditableMathLabel+Responder.m b/mathEditor/editor/MTEditableMathLabel+Responder.m index 968bc00..0e3e4d7 100644 --- a/mathEditor/editor/MTEditableMathLabel+Responder.m +++ b/mathEditor/editor/MTEditableMathLabel+Responder.m @@ -8,8 +8,7 @@ #import #import "MTConfig.h" #import "MTEditableMathLabel.h" - -@import MathEditorSwift; +#import "MTView/MTView+FirstResponder.h" NS_ASSUME_NONNULL_BEGIN diff --git a/mathEditor/editor/MTEditableMathLabel.m b/mathEditor/editor/MTEditableMathLabel.m index 190171c..c0c757b 100644 --- a/mathEditor/editor/MTEditableMathLabel.m +++ b/mathEditor/editor/MTEditableMathLabel.m @@ -14,13 +14,18 @@ #import "MTMathList.h" #import "MTMathUILabel.h" #import "MTMathAtomFactory.h" +#import "MTCancelView.h" #import "MTCaretView.h" #import "MTTapGestureRecognizer.h" +#import "MTMathList+Editing.h" +#import "MTDisplay+Editing.h" +#import "MTView/MTView+AutoLayout.h" +#import "MTView/MTView+Layout.h" +#import "MTView/MTView+FirstResponder.h" + #import "MTUnicode.h" #import "MTMathListBuilder.h" -@import MathEditorSwift; - @interface MTEditableMathLabel() @property (nonatomic) MTMathUILabel* label; @@ -82,7 +87,7 @@ - (void) initialize #endif label.textAlignment = kMTTextAlignmentCenter; self.label = label; - [self createCancelImage]; + // [self createCancelImage]; CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height); _flipTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, -1.0), transform); diff --git a/mathEditor/internal/MTCancelView.h b/mathEditor/internal/MTCancelView.h new file mode 100644 index 0000000..8d243c6 --- /dev/null +++ b/mathEditor/internal/MTCancelView.h @@ -0,0 +1,13 @@ +// +// MTCancelView.h +// +// Created for the editable label clear affordance. +// + +@import iosMath; + +@interface MTCancelView : MTView + +- (instancetype)initWithTarget:(id)target action:(SEL)action; + +@end diff --git a/mathEditor/internal/MTCancelView.m b/mathEditor/internal/MTCancelView.m new file mode 100644 index 0000000..9d40ec9 --- /dev/null +++ b/mathEditor/internal/MTCancelView.m @@ -0,0 +1,50 @@ +// +// MTCancelView.m +// +// Created for the editable label clear affordance. +// + +#import "MTCancelView.h" +#import "MTTapGestureRecognizer.h" +#import "MTView/MTView+AutoLayout.h" + +@interface MTCancelView () + +#if TARGET_OS_IPHONE +@property (nonatomic, strong) UIImageView *imageView; +#else +@property (nonatomic, strong) NSImageView *imageView; +#endif + +@end + +@implementation MTCancelView + +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithFrame:CGRectZero]; + if (self) { +#if TARGET_OS_IPHONE + UIImage *image = [UIImage systemImageNamed:@"xmark.circle"]; + image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + _imageView = [[UIImageView alloc] initWithImage:image]; + _imageView.contentMode = UIViewContentModeScaleAspectFit; + _imageView.tintColor = [MTColor secondaryLabelColor]; +#else + NSImage *image = [NSImage imageWithSystemSymbolName:@"xmark.circle" accessibilityDescription:nil]; + _imageView = [[NSImageView alloc] initWithFrame:CGRectZero]; + _imageView.image = image; + _imageView.imageScaling = NSImageScaleProportionallyUpOrDown; + _imageView.contentTintColor = [MTColor secondaryLabelColor]; +#endif + [self addSubview:_imageView]; + [_imageView pinToSuperview]; + + MTTapGestureRecognizer *tapRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:target action:action]; + [self addGestureRecognizer:tapRecognizer]; + self.hidden = YES; + } + return self; +} + +@end diff --git a/mathEditor/internal/MTCaretView.m b/mathEditor/internal/MTCaretView.m index 01d08ac..1e7b041 100644 --- a/mathEditor/internal/MTCaretView.m +++ b/mathEditor/internal/MTCaretView.m @@ -11,10 +11,10 @@ #import "MTCaretView.h" #import "MTEditableMathLabel.h" #import "MTConfig.h" +#import "MTView/MTView+Layout.h" +#import "MTView/MTView+HitTest.h" #import "NSBezierPath+addLineToPoint.h" -@import MathEditorSwift; - static const NSTimeInterval InitialBlinkDelay = 0.7; static const NSTimeInterval BlinkRate = 0.5; // The settings below make sense for the given font size. They are scaled appropriately when the fontsize changes. diff --git a/mathEditor/internal/MTDisplay+Editing.h b/mathEditor/internal/MTDisplay+Editing.h new file mode 100644 index 0000000..7af2631 --- /dev/null +++ b/mathEditor/internal/MTDisplay+Editing.h @@ -0,0 +1,29 @@ +// +// MTDisplay+Editing.h +// +// Created by Kostub Deshmukh on 9/6/13. +// Copyright (C) 2013 MathChat +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +#import "MTMathListDisplay.h" +#import "MTMathListIndex.h" + +@interface MTDisplay (Editing) + +// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. +- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; + +// The bounds of the display indicated by the given index +- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; + +// Highlight the character(s) at the given index. +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; + +// Highlight the entire display with the given color +- (void) highlightWithColor:(MTColor*) color; + +@end + diff --git a/mathEditor/internal/MTDisplay+Editing.m b/mathEditor/internal/MTDisplay+Editing.m new file mode 100644 index 0000000..a60ef5b --- /dev/null +++ b/mathEditor/internal/MTDisplay+Editing.m @@ -0,0 +1,596 @@ +// +// MTDisplay+Editing.m +// +// Created by Kostub Deshmukh on 9/6/13. +// Copyright (C) 2013 MathChat +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +#import + +#import "MTDisplay+Editing.h" +#import "MTMathList+Editing.h" + +static CGPoint kInvalidPosition = { -1, -1}; +// Number of pixels outside the bound to allow a point to be considered as part of the bounds. +static CGFloat kPixelDelta = 2; + +#pragma mark Unicode functions + +NSUInteger numCodePointsInRange(NSString* str, NSRange range) { + if (range.length > 0) { + // doesn't work correctly if range is 0 + NSRange grown = [str rangeOfComposedCharacterSequencesForRange:range]; + + unichar buffer[grown.length]; + [str getCharacters:buffer range:grown]; + int count = 0; + for (int i = 0; i < grown.length; i++) { + count++; + // we check both high and low due to work for both endianess + if (CFStringIsSurrogateHighCharacter(buffer[i]) || CFStringIsSurrogateLowCharacter(buffer[i])) { + // skip the next character + i++; + } + } + return count; + } + return 0; +} + +static NSUInteger codePointIndexToStringIndex(NSString* str, const NSUInteger codePointIndex) { + unichar buffer[str.length]; + [str getCharacters:buffer range:NSMakeRange(0, str.length)]; + int codePointCount = 0; + for (int i = 0; i < str.length; i++, codePointCount++) { + if (codePointCount == codePointIndex) { + return i; // this is the string index + } + // we check both high and low due to work for both endianess + if (CFStringIsSurrogateHighCharacter(buffer[i]) || CFStringIsSurrogateLowCharacter(buffer[i])) { + // skip the next character + i++; + } + } + + // the index is out of range + return NSNotFound; +} + +#pragma mark - Distance utilities +// Calculates the manhattan distance from a point to the nearest boundary of the rectangle +static CGFloat distanceFromPointToRect(CGPoint point, CGRect rect) { + CGFloat distance = 0; + if (point.x < rect.origin.x) { + distance += (rect.origin.x - point.x); + } else if (point.x > CGRectGetMaxX(rect)) { + distance += point.x - CGRectGetMaxX(rect); + } + + if (point.y < rect.origin.y) { + distance += (rect.origin.y - point.y); + } else if (point.y > CGRectGetMaxY(rect)) { + distance += point.y - CGRectGetMaxY(rect); + } + return distance; +} + +# pragma mark - MTDisplay + +@implementation MTDisplay (Editing) + +// Empty implementations for the base class + +- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point +{ + return nil; +} + +- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index +{ + return kInvalidPosition; +} + +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color +{ +} + +- (void)highlightWithColor:(MTColor *)color +{ +} +@end + +# pragma mark - MTCTLineDisplay + +@interface MTCTLineDisplay (Editing) + +// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. +- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; + +// The bounds of the display indicated by the given index +- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; + +// Highlight the character(s) at the given index. +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; + +- (void)highlightWithColor:(MTColor *)color; + +@end + +@implementation MTCTLineDisplay (Editing) + +- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point +{ + // Convert the point to the reference of the CTLine + CGPoint relativePoint = CGPointMake(point.x - self.position.x, point.y - self.position.y); + CFIndex index = CTLineGetStringIndexForPosition(self.line, relativePoint); + if (index == kCFNotFound) { + return nil; + } + // The index returned is in UTF-16, translate to codepoint index. + // NSUInteger codePointIndex = stringIndexToCodePointIndex(self.attributedString.string, index); + // Convert the code point index to an index into the mathlist + NSUInteger mlIndex = [self convertToMathListIndex:index]; + // index will be between 0 and _range.length inclusive + NSAssert(mlIndex >= 0 && mlIndex <= self.range.length, @"Returned index out of range: %ld, range (%@, %@)", index, @(self.range.location), @(self.range.length)); + // translate to the current index + MTMathListIndex* listIndex = [MTMathListIndex level0Index:self.range.location + mlIndex]; + return listIndex; +} + +- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index +{ + CGFloat offset; + NSAssert(index.subIndexType == kMTSubIndexTypeNone, @"Index in a CTLineDisplay cannot have sub indexes."); + if (index.atomIndex == NSMaxRange(self.range)) { + offset = self.width; + } else { + NSAssert(NSLocationInRange(index.atomIndex, self.range), @"Index %@ not in range %@", index, NSStringFromRange(self.range)); + NSUInteger strIndex = [self mathListIndexToStringIndex:index.atomIndex - self.range.location]; + //CFIndex charIndex = codePointIndexToStringIndex(self.attributedString.string, strIndex); + offset = CTLineGetOffsetForStringIndex(self.line, strIndex, NULL); + } + return CGPointMake(self.position.x + offset, self.position.y); +} + + +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color +{ + assert(NSLocationInRange(index.atomIndex, self.range)); + assert(index.subIndexType == kMTSubIndexTypeNone || index.subIndexType == kMTSubIndexTypeNucleus); + if (index.subIndexType == kMTSubIndexTypeNucleus) { + NSAssert(false, @"Nucleus highlighting not supported yet"); + } + // index is in unicode code points, while attrString is not + CFIndex charIndex = codePointIndexToStringIndex(self.attributedString.string, index.atomIndex - self.range.location); + assert(charIndex != NSNotFound); + + NSMutableAttributedString* attrStr = self.attributedString.mutableCopy; + [attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[color CGColor] + range:[attrStr.string rangeOfComposedCharacterSequenceAtIndex:charIndex]]; + self.attributedString = attrStr; +} + +- (void)highlightWithColor:(MTColor *)color +{ + NSMutableAttributedString* attrStr = self.attributedString.mutableCopy; + [attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[color CGColor] + range:NSMakeRange(0, attrStr.length)]; + self.attributedString = attrStr; +} + +// Convert the index into the current string to an index into the mathlist. These may not be the same since a single +// math atom may have multiple characters. +- (NSUInteger) convertToMathListIndex:(NSUInteger) strIndex +{ + NSUInteger strLenCovered = 0; + for (NSUInteger mlIndex = 0; mlIndex < self.atoms.count; mlIndex++) { + if (strLenCovered >= strIndex) { + return mlIndex; + } + MTMathAtom* atom = self.atoms[mlIndex]; + strLenCovered += atom.nucleus.length; + } + // By the time we come to the end of the string, we should have covered all the characters. + NSAssert(strLenCovered >= strIndex, @"StrIndex should not be more than the len covered"); + return self.atoms.count; +} + +- (NSUInteger) mathListIndexToStringIndex:(NSUInteger) mlIndex +{ + NSAssert(mlIndex < self.atoms.count, @"Index %@ not in range %@", @(mlIndex), @(self.atoms.count)); + NSUInteger strIndex = 0; + for (NSUInteger i = 0; i < mlIndex; i++) { + MTMathAtom* atom = self.atoms[i]; + strIndex += atom.nucleus.length; + } + return strIndex; +} + +@end + +#pragma mark - MTFractionDisplay + +@interface MTFractionDisplay (Editing) + +// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. +- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; + +// The bounds of the display indicated by the given index +- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; + +// Highlight the character(s) at the given index. +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; + +- (void)highlightWithColor:(MTColor *)color; + +- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type; + +@end + +@implementation MTFractionDisplay (Editing) + +- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point +{ + // We can be before the fraction or after the fraction + if (point.x < self.position.x - kPixelDelta) { + // we are before the fraction, so + return [MTMathListIndex level0Index:self.range.location]; + } else if (point.x > self.position.x + self.width + kPixelDelta) { + // we are after the fraction + return [MTMathListIndex level0Index:NSMaxRange(self.range)]; + } else { + // we can be either near the numerator or the denominator + CGFloat numeratorDistance = distanceFromPointToRect(point, self.numerator.displayBounds); + CGFloat denominatorDistance = distanceFromPointToRect(point, self.denominator.displayBounds); + if (numeratorDistance < denominatorDistance) { + return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.numerator closestIndexToPoint:point] type:kMTSubIndexTypeNumerator]; + } else { + return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.denominator closestIndexToPoint:point] type:kMTSubIndexTypeDenominator]; + } + } +} + +// Seems never used +- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index +{ + assert(index.subIndexType == kMTSubIndexTypeNone); + // draw a caret before the fraction + return CGPointMake(self.position.x, self.position.y); +} + +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color +{ + assert(index.subIndexType == kMTSubIndexTypeNone); + [self highlightWithColor:color]; +} + +- (void)highlightWithColor:(MTColor *)color +{ + [self.numerator highlightWithColor:color]; + [self.denominator highlightWithColor:color]; +} + +- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type +{ + switch (type) { + case kMTSubIndexTypeNumerator: + return self.numerator; + + case kMTSubIndexTypeDenominator: + return self.denominator; + + case kMTSubIndexTypeDegree: + case kMTSubIndexTypeRadicand: + case kMTSubIndexTypeNucleus: + case kMTSubIndexTypeSubscript: + case kMTSubIndexTypeSuperscript: + case kMTSubIndexTypeNone: + NSAssert(false, @"Not a fraction subtype %d", type); + return nil; + } +} + +@end + +#pragma mark - MTRadicalDisplay + +@interface MTRadicalDisplay (Editing) + +// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. +- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; + +// The bounds of the display indicated by the given index +- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; + +// Highlight the character(s) at the given index. +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; + +- (void)highlightWithColor:(MTColor *)color; + +- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type; + +@end + +@implementation MTRadicalDisplay (Editing) + + +- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point +{ + // We can be before the radical or after the radical + if (point.x < self.position.x - kPixelDelta) { + // we are before the radical, so + return [MTMathListIndex level0Index:self.range.location]; + } else if (point.x > self.position.x + self.width + kPixelDelta) { + // we are after the radical + return [MTMathListIndex level0Index:NSMaxRange(self.range)]; + } else { + // we are in the radical + CGFloat degreeDistance = distanceFromPointToRect(point, self.degree.displayBounds); + CGFloat radicandDistance = distanceFromPointToRect(point, self.radicand.displayBounds); + + if (degreeDistance < radicandDistance) { + return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.degree closestIndexToPoint:point] type:kMTSubIndexTypeDegree]; + } else { + return [MTMathListIndex indexAtLocation:self.range.location withSubIndex:[self.radicand closestIndexToPoint:point] type:kMTSubIndexTypeRadicand]; + } + + } +} + +// TODO seems unused +- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index +{ + assert(index.subIndexType == kMTSubIndexTypeNone); + // draw a caret + return CGPointMake(self.position.x, self.position.y); +} + +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color +{ + assert(index.subIndexType == kMTSubIndexTypeNone); + [self highlightWithColor:color]; +} + +- (void)highlightWithColor:(MTColor *)color +{ + [self.radicand highlightWithColor:color]; +} + +- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type +{ + switch (type) { + + case kMTSubIndexTypeRadicand: + return self.radicand; + case kMTSubIndexTypeDegree: + return self.degree; + case kMTSubIndexTypeNumerator: + + case kMTSubIndexTypeDenominator: + + case kMTSubIndexTypeNucleus: + case kMTSubIndexTypeSubscript: + case kMTSubIndexTypeSuperscript: + case kMTSubIndexTypeNone: + NSAssert(false, @"Not a radical subtype %d", type); + return nil; + } +} + +@end + +#pragma mark - MTMathListDisplay + +@interface MTMathListDisplay (Editing) + +// Find the index in the mathlist before which a new character should be inserted. Returns nil if it cannot find the index. +- (MTMathListIndex*) closestIndexToPoint:(CGPoint) point; + +// The bounds of the display indicated by the given index +- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index; + +// Highlight the character(s) at the given index. +- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color; + +- (void)highlightWithColor:(MTColor *)color; + +@end + +@implementation MTMathListDisplay (Editing) + + +- (MTMathListIndex *)closestIndexToPoint:(CGPoint)point +{ + // the origin of for the subelements of a MTMathList is the current position, so translate the current point to our origin. + CGPoint translatedPoint = CGPointMake(point.x - self.position.x, point.y - self.position.y); + + MTDisplay* closest = nil; + NSMutableArray* xbounds = [NSMutableArray array]; + CGFloat minDistance = CGFLOAT_MAX; + for (MTDisplay* atom in self.subDisplays) { + CGRect bounds = atom.displayBounds; + CGFloat maxBoundsX = CGRectGetMaxX(bounds); + + if (bounds.origin.x - kPixelDelta <= translatedPoint.x && translatedPoint.x <= maxBoundsX + kPixelDelta) { + [xbounds addObject:atom]; + } + + CGFloat distance = distanceFromPointToRect(translatedPoint, bounds); + if (distance < minDistance) { + closest = atom; + minDistance = distance; + } + } + MTDisplay* atomWithPoint = nil; + if (xbounds.count == 0) { + if (translatedPoint.x <= -kPixelDelta) { + // all the way to the left + return [MTMathListIndex level0Index:self.range.location]; + } else if (translatedPoint.x >= self.width + kPixelDelta) { + // all the way to the right + return [MTMathListIndex level0Index:NSMaxRange(self.range)]; + } else { + // It is within the mathlist but not within the x bounds of any sublist. Use the closest in that case. + atomWithPoint = closest; + } + } else if (xbounds.count == 1) { + atomWithPoint = xbounds[0]; + if (translatedPoint.x >= self.width - kPixelDelta) { + // The point is close to the end. Only use the selected x bounds if the y is within range. + if (translatedPoint.y <= CGRectGetMinY(atomWithPoint.displayBounds) - kPixelDelta) { + // The point is less than the y including the delta. Move the cursor to the end rather than in this atom. + return [MTMathListIndex level0Index:NSMaxRange(self.range)]; + } + } + } else { + // Use the closest since there are more than 2 sublists which have this x position. + atomWithPoint = closest; + } + + if (!atomWithPoint) { + return nil; + } + + MTMathListIndex* index = [atomWithPoint closestIndexToPoint:(CGPoint)translatedPoint]; + if ([atomWithPoint isKindOfClass:[MTMathListDisplay class]]) { + MTMathListDisplay* closestLine = (MTMathListDisplay*) atomWithPoint; + NSAssert(closestLine.type == kMTLinePositionSubscript || closestLine.type == kMTLinePositionSuperscript, @"MTLine type regular inside an MTLine - shouldn't happen"); + // This is a subscript or a superscript, return the right type of subindex + MTMathListSubIndexType type = (closestLine.type == kMTLinePositionSubscript) ? kMTSubIndexTypeSubscript : kMTSubIndexTypeSuperscript; + // The index of the atom this denotes. + NSAssert(closestLine.index != NSNotFound, @"Index is not set for a subscript/superscript"); + return [MTMathListIndex indexAtLocation:closestLine.index withSubIndex:index type:type]; + } else if (atomWithPoint.hasScript) { + // The display list has a subscript or a superscript. If the index is at the end of the atom, then we need to put it before the sub/super script rather than after. + if (index.atomIndex == NSMaxRange(atomWithPoint.range)) { + return [MTMathListIndex indexAtLocation:index.atomIndex - 1 withSubIndex:[MTMathListIndex level0Index:1] type:kMTSubIndexTypeNucleus]; + } + } + return index; +} + +- (MTDisplay*) subAtomForIndex:(MTMathListIndex*) index +{ + // Inside the range + if (index.subIndexType == kMTSubIndexTypeSuperscript || index.subIndexType == kMTSubIndexTypeSubscript) { + for (MTDisplay* atom in self.subDisplays) { + if ([atom isKindOfClass:[MTMathListDisplay class]]) { + MTMathListDisplay* lineAtom = (MTMathListDisplay*) atom; + if (index.atomIndex == lineAtom.index) { + // this is the right character for the sub/superscript + // Check that it's type matches the index + if ((lineAtom.type == kMTLinePositionSubscript && index.subIndexType == kMTSubIndexTypeSubscript) + || (lineAtom.type == kMTLinePositionSuperscript && index.subIndexType == kMTSubIndexTypeSuperscript)) { + return lineAtom; + } + } + } + } + } else { + for (MTDisplay* atom in self.subDisplays) { + if (![atom isKindOfClass:[MTMathListDisplay class]] && NSLocationInRange(index.atomIndex, atom.range)) { + // not a subscript/superscript and + // jackpot, the index is in the range of this atom. + switch (index.subIndexType) { + case kMTSubIndexTypeNone: + case kMTSubIndexTypeNucleus: + return atom; + + case kMTSubIndexTypeDegree: + case kMTSubIndexTypeRadicand: + if ([atom isKindOfClass:[MTRadicalDisplay class]]) { + MTRadicalDisplay *radical = (MTRadicalDisplay *) atom; + return [radical subAtomForIndexType:index.subIndexType]; + } else { + NSLog(@"No radical at index: %lu", (unsigned long)index.atomIndex); + return nil; + } + + case kMTSubIndexTypeNumerator: + case kMTSubIndexTypeDenominator: + if ([atom isKindOfClass:[MTFractionDisplay class]]) { + MTFractionDisplay* frac = (MTFractionDisplay*) atom; + return [frac subAtomForIndexType:index.subIndexType]; + } else { + NSLog(@"No fraction at index: %lu", (unsigned long)index.atomIndex); + return nil; + } + + case kMTSubIndexTypeSubscript: + case kMTSubIndexTypeSuperscript: + assert(false); // Can't happen + break; + } + // We found the right subatom + break; + } + } + } + return nil; +} + +- (CGPoint)caretPositionForIndex:(MTMathListIndex *)index +{ + CGPoint position = kInvalidPosition; + if (!index) { + return kInvalidPosition; + } + + if (index.atomIndex == NSMaxRange(self.range)) { + // Special case the edge of the range + position = CGPointMake(self.width, 0); + } else if (NSLocationInRange(index.atomIndex, self.range)) { + MTDisplay* atom = [self subAtomForIndex:index]; + if (index.subIndexType == kMTSubIndexTypeNucleus) { + NSUInteger nucleusPosition = index.atomIndex + index.subIndex.atomIndex; + position = [atom caretPositionForIndex:[MTMathListIndex level0Index:nucleusPosition]]; + } else if (index.subIndexType == kMTSubIndexTypeNone) { + position = [atom caretPositionForIndex:index]; + } else { + // recurse + position = [atom caretPositionForIndex:index.subIndex]; + } + } else { + // outside the range + return kInvalidPosition; + } + + if (CGPointEqualToPoint(position, kInvalidPosition)) { + // we didn't find the position + return position; + } + + // convert bounds from our coordinate system before returning + position.x += self.position.x; + position.y += self.position.y; + return position; +} + + +- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color +{ + if (!index) { + return; + } + if (NSLocationInRange(index.atomIndex, self.range)) { + MTDisplay* atom = [self subAtomForIndex:index]; + if (index.subIndexType == kMTSubIndexTypeNucleus || index.subIndexType == kMTSubIndexTypeNone) { + [atom highlightCharacterAtIndex:index color:color]; + } else { + // recurse + [atom highlightCharacterAtIndex:index.subIndex color:color]; + } + } +} + +- (void)highlightWithColor:(MTColor *)color +{ + for (MTDisplay* atom in self.subDisplays) { + [atom highlightWithColor:color]; + } +} + +@end diff --git a/mathEditor/internal/MTMathList+Editing.h b/mathEditor/internal/MTMathList+Editing.h new file mode 100644 index 0000000..00de7c1 --- /dev/null +++ b/mathEditor/internal/MTMathList+Editing.h @@ -0,0 +1,26 @@ +// +// MTMathList+Editing.h +// +// Created by Kostub Deshmukh on 9/5/13. +// Copyright (C) 2013 MathChat +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +#import "MTMathList.h" +#import "MTMathListIndex.h" + +@interface MTMathList (Editing) + +- (void) insertAtom:(MTMathAtom *)atom atListIndex:(MTMathListIndex*) index; + +- (void) removeAtomAtListIndex:(MTMathListIndex *)index; + +- (void) removeAtomsInListIndexRange:(MTMathListRange*) range; + +// Get the atom at the given index. If there is none, or index is invalid returns nil. +- (MTMathAtom*) atomAtListIndex:(MTMathListIndex*) index; + + +@end diff --git a/mathEditor/internal/MTMathList+Editing.m b/mathEditor/internal/MTMathList+Editing.m new file mode 100644 index 0000000..a7a22e5 --- /dev/null +++ b/mathEditor/internal/MTMathList+Editing.m @@ -0,0 +1,286 @@ +// +// MTMathList+Editing.m +// +// Created by Kostub Deshmukh on 9/5/13. +// Copyright (C) 2013 MathChat +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +#import "MTMathList+Editing.h" + +#pragma mark - MTMathList + +@implementation MTMathList (Editing) + +- (void)insertAtom:(MTMathAtom *)atom atListIndex:(MTMathListIndex *)index +{ + if (index.atomIndex > self.atoms.count) { + @throw [NSException exceptionWithName:NSRangeException + reason:[NSString stringWithFormat:@"Index %lu is out of bounds for list of size %lu", (unsigned long)index.atomIndex, (unsigned long)self.atoms.count] + userInfo:nil]; + } + + switch (index.subIndexType) { + case kMTSubIndexTypeNone: + [self insertAtom:atom atIndex:index.atomIndex]; + break; + + case kMTSubIndexTypeNucleus: { + MTMathAtom* currentAtom = [self.atoms objectAtIndex:index.atomIndex]; + NSAssert(currentAtom.subScript || currentAtom.superScript, @"Nuclear fusion is not supported if there are no subscripts or superscripts."); + NSAssert(!atom.subScript && !atom.superScript, @"Cannot fuse with an atom that already has a subscript or a superscript"); + + atom.subScript = currentAtom.subScript; + atom.superScript = currentAtom.superScript; + currentAtom.subScript = nil; + currentAtom.superScript = nil; + [self insertAtom:atom atIndex:index.atomIndex + index.subIndex.atomIndex]; + break; + } + + case kMTSubIndexTypeDegree: + case kMTSubIndexTypeRadicand: { + MTRadical *radical = [self.atoms objectAtIndex:index.atomIndex]; + if (!radical || radical.type != kMTMathAtomRadical) { + // Not radical, quit + NSAssert(false, @"No radical found at index %lu", (unsigned long)index.atomIndex); + return; + } + if (index.subIndexType == kMTSubIndexTypeDegree) { + [radical.degree insertAtom:atom atListIndex:index.subIndex]; + } else { + [radical.radicand insertAtom:atom atListIndex:index.subIndex]; + } + break; + } + + case kMTSubIndexTypeDenominator: + case kMTSubIndexTypeNumerator: { + MTFraction* frac = [self.atoms objectAtIndex:index.atomIndex]; + if (!frac || frac.type != kMTMathAtomFraction) { + NSAssert(false, @"No fraction found at index %lu", (unsigned long)index.atomIndex); + return; + } + if (index.subIndexType == kMTSubIndexTypeNumerator) { + [frac.numerator insertAtom:atom atListIndex:index.subIndex]; + } else { + [frac.denominator insertAtom:atom atListIndex:index.subIndex]; + } + break; + } + + case kMTSubIndexTypeSubscript: { + MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; + NSAssert(current.subScript, @"No subscript for atom at index %lu", (unsigned long)index.atomIndex); + [current.subScript insertAtom:atom atListIndex:index.subIndex]; + break; + } + + case kMTSubIndexTypeSuperscript: { + MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; + NSAssert(current.superScript, @"No superscript for atom at index %lu", (unsigned long)index.atomIndex); + [current.superScript insertAtom:atom atListIndex:index.subIndex]; + break; + } + } +} + +-(void)removeAtomAtListIndex:(MTMathListIndex *)index +{ + if (index.atomIndex >= self.atoms.count) { + @throw [NSException exceptionWithName:NSRangeException + reason:[NSString stringWithFormat:@"Index %lu is out of bounds for list of size %lu", (unsigned long)index.atomIndex, (unsigned long)self.atoms.count] + userInfo:nil]; + } + + switch (index.subIndexType) { + case kMTSubIndexTypeNone: + [self removeAtomAtIndex:index.atomIndex]; + break; + + case kMTSubIndexTypeNucleus: { + MTMathAtom* currentAtom = [self.atoms objectAtIndex:index.atomIndex]; + NSAssert(currentAtom.subScript || currentAtom.superScript, @"Nuclear fission is not supported if there are no subscripts or superscripts."); + MTMathAtom* previous = nil; + if (index.atomIndex > 0) { + previous = [self.atoms objectAtIndex:index.atomIndex - 1]; + } + if (previous && !previous.subScript && !previous.superScript) { + previous.superScript = currentAtom.superScript; + previous.subScript = currentAtom.subScript; + [self removeAtomAtIndex:index.atomIndex]; + } else { + // no previous atom or the previous atom sucks (has sub/super scripts) + currentAtom.nucleus = @""; + } + break; + } + + case kMTSubIndexTypeRadicand: + case kMTSubIndexTypeDegree: { + MTRadical *radical = [self.atoms objectAtIndex:index.atomIndex]; + if (!radical || radical.type != kMTMathAtomRadical) { + // Not radical, quit + NSAssert(false, @"No radical found at index %lu", (unsigned long)index.atomIndex); + return; + } + if (index.subIndexType == kMTSubIndexTypeDegree) { + [radical.degree removeAtomAtListIndex:index.subIndex]; + } else { + [radical.radicand removeAtomAtListIndex:index.subIndex]; + } + + break; + } + + case kMTSubIndexTypeDenominator: + case kMTSubIndexTypeNumerator: { + MTFraction* frac = [self.atoms objectAtIndex:index.atomIndex]; + if (!frac || frac.type != kMTMathAtomFraction) { + NSAssert(false, @"No fraction found at index %lu", (unsigned long)index.atomIndex); + return; + } + if (index.subIndexType == kMTSubIndexTypeNumerator) { + [frac.numerator removeAtomAtListIndex:index.subIndex]; + } else { + [frac.denominator removeAtomAtListIndex:index.subIndex]; + } + break; + } + + case kMTSubIndexTypeSubscript: { + MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; + NSAssert(current.subScript, @"No subscript for atom at index %lu", (unsigned long)index.atomIndex); + [current.subScript removeAtomAtListIndex:index.subIndex]; + break; + } + + case kMTSubIndexTypeSuperscript: { + MTMathAtom* current = [self.atoms objectAtIndex:index.atomIndex]; + NSAssert(current.superScript, @"No superscript for atom at index %lu", (unsigned long)index.atomIndex); + [current.superScript removeAtomAtListIndex:index.subIndex]; + break; + } + } +} + +- (void) removeAtomsInListIndexRange:(MTMathListRange*) range +{ + MTMathListIndex* start = range.start; + + switch (start.subIndexType) { + case kMTSubIndexTypeNone: + [self removeAtomsInRange:NSMakeRange(start.atomIndex, range.length)]; + break; + + case kMTSubIndexTypeNucleus: + NSAssert(false, @"Nuclear fission is not supported"); + break; + + case kMTSubIndexTypeRadicand: + case kMTSubIndexTypeDegree: { + MTRadical *radical = [self.atoms objectAtIndex:start.atomIndex]; + if (!radical || radical.type != kMTMathAtomRadical) { + // Not radical, quit + NSAssert(false, @"No radical found at index %lu", (unsigned long)start.atomIndex); + return; + } + if (start.subIndexType == kMTSubIndexTypeDegree) { + [radical.degree removeAtomsInListIndexRange:range.subIndexRange]; + } else { + [radical.radicand removeAtomsInListIndexRange:range.subIndexRange]; + } + break; + } + + case kMTSubIndexTypeDenominator: + case kMTSubIndexTypeNumerator: { + MTFraction* frac = [self.atoms objectAtIndex:start.atomIndex]; + if (!frac || frac.type != kMTMathAtomFraction) { + NSAssert(false, @"No fraction found at index %lu", (unsigned long)start.atomIndex); + return; + } + if (start.subIndexType == kMTSubIndexTypeNumerator) { + [frac.numerator removeAtomsInListIndexRange:range.subIndexRange]; + } else { + [frac.denominator removeAtomsInListIndexRange:range.subIndexRange]; + } + break; + } + + case kMTSubIndexTypeSubscript: { + MTMathAtom* current = [self.atoms objectAtIndex:start.atomIndex]; + NSAssert(current.subScript, @"No subscript for atom at index %lu", (unsigned long)start.atomIndex); + [current.subScript removeAtomsInListIndexRange:range.subIndexRange]; + break; + } + + case kMTSubIndexTypeSuperscript: { + MTMathAtom* current = [self.atoms objectAtIndex:start.atomIndex]; + NSAssert(current.superScript, @"No superscript for atom at index %lu", (unsigned long)start.atomIndex); + [current.superScript removeAtomsInListIndexRange:range.subIndexRange]; + break; + } + } +} + +- (MTMathAtom *)atomAtListIndex:(MTMathListIndex *)index +{ + if (index == nil) { + return nil; + } + + if (index.atomIndex >= self.atoms.count) { + return nil; + } + + MTMathAtom* atom = self.atoms[index.atomIndex]; + + switch (index.subIndexType) { + case kMTSubIndexTypeNone: + case kMTSubIndexTypeNucleus: + return atom; + + case kMTSubIndexTypeSubscript: + return [atom.subScript atomAtListIndex:index.subIndex]; + + case kMTSubIndexTypeSuperscript: + return [atom.superScript atomAtListIndex:index.subIndex]; + + case kMTSubIndexTypeRadicand: + case kMTSubIndexTypeDegree: { + if (atom.type == kMTMathAtomRadical) { + MTRadical *radical = (MTRadical *) atom; + if (index.subIndexType == kMTSubIndexTypeDegree) { + return [radical.degree atomAtListIndex:index.subIndex]; + } else { + return [radical.radicand atomAtListIndex:index.subIndex]; + } + } else { + // No radical at this index + return nil; + } + } + + case kMTSubIndexTypeNumerator: + case kMTSubIndexTypeDenominator: + { + if (atom.type == kMTMathAtomFraction) { + MTFraction* frac = (MTFraction*) atom; + if (index.subIndexType == kMTSubIndexTypeDenominator) { + return [frac.denominator atomAtListIndex:index.subIndex]; + } else { + return [frac.numerator atomAtListIndex:index.subIndex]; + } + + } else { + // No fraction at this index. + return nil; + } + } + } +} + +@end diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.h b/mathEditor/internal/MTView/MTView+AutoLayout.h new file mode 100644 index 0000000..62cf81b --- /dev/null +++ b/mathEditor/internal/MTView/MTView+AutoLayout.h @@ -0,0 +1,20 @@ +// +// MTView+AutoLayout.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MXView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXView (AutoLayout) + +- (void)pinToSuperview; + +- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing; + +@end + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+AutoLayout.m b/mathEditor/internal/MTView/MTView+AutoLayout.m new file mode 100644 index 0000000..be85c28 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+AutoLayout.m @@ -0,0 +1,32 @@ +// +// MTView+AutoLayout.m +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTConfig.h" +#import "MTView+AutoLayout.h" + +@implementation MXView (AutoLayout) + +- (void)pinToSuperview +{ + [self pinToSuperviewWithTop:0 leading:0 bottom:0 trailing:0]; +} + +- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing +{ + MTView *superview = self.superview; + if (!superview) + return; + self.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [self.topAnchor constraintEqualToAnchor:superview.topAnchor constant:top], + [self.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor constant:leading], + [self.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor constant:-trailing], + [self.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor constant:-bottom] + ]]; +} + +@end diff --git a/mathEditor/internal/MTView/MTView+FirstResponder.h b/mathEditor/internal/MTView/MTView+FirstResponder.h new file mode 100644 index 0000000..67a2b94 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+FirstResponder.h @@ -0,0 +1,22 @@ +// +// MTView+FirstResponder.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +#if TARGET_OS_OSX + +@interface NSView (FirstResponder) + +@property (nonatomic, readonly) BOOL isFirstResponder; + +@end + +#endif // TARGET_OS_OSX + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+FirstResponder.m b/mathEditor/internal/MTView/MTView+FirstResponder.m new file mode 100644 index 0000000..df4cda9 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+FirstResponder.m @@ -0,0 +1,26 @@ +// +// MTView+FirstResponder.m +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTConfig.h" +#import "MTView+FirstResponder.h" + +NS_ASSUME_NONNULL_BEGIN + +#if TARGET_OS_OSX + +@implementation NSView (FirstResponder) + +- (BOOL)isFirstResponder +{ + return self.window.firstResponder == self; +} + +@end + +#endif + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+HitTest.h b/mathEditor/internal/MTView/MTView+HitTest.h new file mode 100644 index 0000000..0c87d63 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+HitTest.h @@ -0,0 +1,23 @@ +// +// MTView+HitTest.h +// MathEditor +// +// Created by Madiyar Aitbayev on 21/03/2026. +// + +#if TARGET_OS_OSX + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSView (HitTest) + +- (NSView *)hitTestOutsideBounds:(NSPoint)point; +- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/mathEditor/internal/MTView/MTView+HitTest.m b/mathEditor/internal/MTView/MTView+HitTest.m new file mode 100644 index 0000000..59822b5 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+HitTest.m @@ -0,0 +1,46 @@ +// +// MTView+HitTest.m +// MathEditor +// +// Created by Madiyar Aitbayev on 21/03/2026. +// + +#import "MTView+HitTest.h" + +#if TARGET_OS_OSX + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSView (HitTest) + +- (NSView *)hitTestOutsideBounds:(NSPoint)point +{ + return [self hitTestOutsideBounds:point ignoringSubviews:@[]]; +} + +- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews +{ + if (self.hidden) { + return nil; + } + NSPoint localPoint = [self convertPoint:point fromView:self.superview]; + for (NSView *child in [self.subviews reverseObjectEnumerator]) { + if ([ignoredSubviews containsObject:child]) { + continue; + } + NSView *hitView = [child hitTest:localPoint]; + if (hitView) { + return hitView; + } + } + if (NSPointInRect(localPoint, self.bounds)) { + return self; + } + return nil; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/mathEditor/internal/MTView/MTView+Layout.h b/mathEditor/internal/MTView/MTView+Layout.h new file mode 100644 index 0000000..fdeb22b --- /dev/null +++ b/mathEditor/internal/MTView/MTView+Layout.h @@ -0,0 +1,28 @@ +// +// MTView+Layout.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MXView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXView (Layout) + +#if TARGET_OS_OSX + +- (void)setNeedsLayout; + +- (void)setNeedsDisplay; + +- (void)layoutIfNeeded; + +- (void)bringSubviewToFront:(NSView *)view; + +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/mathEditor/internal/MTView/MTView+Layout.m b/mathEditor/internal/MTView/MTView+Layout.m new file mode 100644 index 0000000..0d43c50 --- /dev/null +++ b/mathEditor/internal/MTView/MTView+Layout.m @@ -0,0 +1,40 @@ +// +// MTView+Layout.m +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#import "MTView+Layout.h" + +@implementation MXView (Layout) + +#if TARGET_OS_OSX + +- (void)setNeedsLayout +{ + [self setNeedsLayout:YES]; +} + +- (void)setNeedsDisplay +{ + [self setNeedsDisplay:YES]; +} + +- (void)layoutIfNeeded +{ + [self layoutSubtreeIfNeeded]; +} + +- (void)bringSubviewToFront:(NSView *)view +{ + if (view.superview != self) { + return; + } + [view removeFromSuperview]; + [self addSubview:view]; +} + +#endif + +@end diff --git a/mathEditor/internal/MTView/MXView.h b/mathEditor/internal/MTView/MXView.h new file mode 100644 index 0000000..d781e72 --- /dev/null +++ b/mathEditor/internal/MTView/MXView.h @@ -0,0 +1,14 @@ +// +// MXView.h +// MathEditor +// +// Created by Madiyar Aitbayev on 20/03/2026. +// + +#if TARGET_OS_OSX +#import +#define MXView NSView +#else +#import +#define MXView UIView +#endif From 261d50e8e18a040cb15f7417c1f13a312c4a9f42 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 13:16:54 +0000 Subject: [PATCH 109/133] revert --- MathEditor.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MathEditor.xcodeproj/project.pbxproj b/MathEditor.xcodeproj/project.pbxproj index 2abfebe..593056d 100644 --- a/MathEditor.xcodeproj/project.pbxproj +++ b/MathEditor.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 490BE5561CE695CC00AE31A0 /* libMathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMathEditor.a; sourceTree = BUILT_PRODUCTS_DIR; }; 490BE5771CE6A08100AE31A0 /* MTDisplayEditingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTDisplayEditingTest.m; sourceTree = ""; }; 497F92211CF8CBFF00022162 /* MathEditor-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MathEditor-Prefix.pch"; sourceTree = ""; }; + 49DEC81E1CF5523E000053CD /* MTCaretView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTCaretView.h; sourceTree = ""; }; 49DEC81F1CF5523E000053CD /* MTCaretView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTCaretView.m; sourceTree = ""; }; 49DEC8201CF5523E000053CD /* MTDisplay+Editing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTDisplay+Editing.h"; sourceTree = ""; }; 49DEC8211CF5523E000053CD /* MTDisplay+Editing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTDisplay+Editing.m"; sourceTree = ""; }; @@ -135,6 +136,7 @@ 49DEC81D1CF5523E000053CD /* internal */ = { isa = PBXGroup; children = ( + 49DEC81E1CF5523E000053CD /* MTCaretView.h */, 49DEC81F1CF5523E000053CD /* MTCaretView.m */, 49DEC8201CF5523E000053CD /* MTDisplay+Editing.h */, 49DEC8211CF5523E000053CD /* MTDisplay+Editing.m */, From 358a92b2dc599f445ec76089ca5b77cc50f153ba Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 14:15:00 +0000 Subject: [PATCH 110/133] MathKeyboardSwiftUI uses MathKeyboardSwift --- .../MTEditableMathLabelSwift+Responder.swift | 2 +- .../MTEditableMathLabelSwift.swift | 12 ++---- .../Sources/MathEditorSwift/MTKeyInput.swift | 19 ++++++++ .../regressions.md | 0 todo.md => MathEditorSwift/todo.md | 0 .../project.pbxproj | 43 ++++++++----------- .../ContentView.swift | 10 ++--- .../MathEditorSwiftUIExample.entitlements | 7 +-- MathKeyboardSwiftUI/Package.swift | 4 +- .../Keyboards/FunctionsKeyboardView.swift | 3 +- .../Keyboards/MainKeyboardView.swift | 3 +- .../MTMathKeyboardSwiftUIRootView.swift | 8 ++-- 12 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift rename regressions.md => MathEditorSwift/regressions.md (100%) rename todo.md => MathEditorSwift/todo.md (100%) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift index abc8554..118451e 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift @@ -3,7 +3,7 @@ import Foundation #if canImport(UIKit) import UIKit - extension MTEditableMathLabelSwift: UIKeyInput { + extension MTEditableMathLabelSwift { public override var inputView: UIView? { keyboard as? UIView } diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index d16a19c..8285b54 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -41,12 +41,6 @@ public protocol MTMathKeyboardTraitsSwift: AnyObject { var radicalHighlighted: Bool { get set } } -public protocol MTKeyInputSwift: AnyObject { - func insertText(_ text: String) - func deleteBackward() - var hasText: Bool { get } -} - /// Any keyboard that provides input to the `MTEditableMathUILabel` must implement /// this protocol. /// @@ -55,12 +49,12 @@ public protocol MTKeyInputSwift: AnyObject { /// /// This protocol inherits from `MTMathKeyboardTraits`. public protocol MTMathKeyboardSwift: MTMathKeyboardTraitsSwift { - func startedEditing(_ label: MTView & MTKeyInputSwift) - func finishedEditing(_ label: MTView & MTKeyInputSwift) + func startedEditing(_ label: MTView & MTKeyInput) + func finishedEditing(_ label: MTView & MTKeyInput) } @objc(MTEditableMathLabelSwift) -public final class MTEditableMathLabelSwift: MTView, MTKeyInputSwift { +public final class MTEditableMathLabelSwift: MTView, MTKeyInput { @objc public var mathList: MTMathList = MTMathList() { didSet { label.mathList = mathList diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift b/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift new file mode 100644 index 0000000..66661b9 --- /dev/null +++ b/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift @@ -0,0 +1,19 @@ +// +// MTKeyInput.swift +// MathEditorSwift +// +// Created by Madiyar Aitbayev on 26/03/2026. +// + + +#if canImport(UIKit) +import UIKit +public typealias MTKeyInput = UIKeyInput +#else +public protocol MTKeyInput { + var hasText: Bool { get } + func insertText(_ text: String) + func deleteBackward() +} +#endif + diff --git a/regressions.md b/MathEditorSwift/regressions.md similarity index 100% rename from regressions.md rename to MathEditorSwift/regressions.md diff --git a/todo.md b/MathEditorSwift/todo.md similarity index 100% rename from todo.md rename to MathEditorSwift/todo.md diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj index dee3e76..7ae018e 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj @@ -7,9 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28F72E2D525900F9B6FE /* MathEditor */; }; - 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28FA2E2D527100F9B6FE /* MathEditor */; }; - C1489ED52F6CB33A00AB2993 /* MathKeyboard in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = C1489ED42F6CB33A00AB2993 /* MathKeyboard */; }; + 86E17B822F7569EF0079CEC2 /* MathEditorSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 86E17B812F7569EF0079CEC2 /* MathEditorSwift */; }; C1DAAE012F70231100E4E983 /* MathKeyboardSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = C1DAAE002F70231100E4E983 /* MathKeyboardSwiftUI */; }; /* End PBXBuildFile section */ @@ -30,10 +28,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C1489ED52F6CB33A00AB2993 /* MathKeyboard in Frameworks */, C1DAAE012F70231100E4E983 /* MathKeyboardSwiftUI in Frameworks */, - 862D28F82E2D525900F9B6FE /* MathEditor in Frameworks */, - 862D28FB2E2D527100F9B6FE /* MathEditor in Frameworks */, + 86E17B822F7569EF0079CEC2 /* MathEditorSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -84,10 +80,8 @@ ); name = MathEditorSwiftUIExample; packageProductDependencies = ( - 862D28F72E2D525900F9B6FE /* MathEditor */, - 862D28FA2E2D527100F9B6FE /* MathEditor */, - C1489ED42F6CB33A00AB2993 /* MathKeyboard */, C1DAAE002F70231100E4E983 /* MathKeyboardSwiftUI */, + 86E17B812F7569EF0079CEC2 /* MathEditorSwift */, ); productName = MathEditorSwiftUIExample; productReference = 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */; @@ -101,7 +95,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1640; - LastUpgradeCheck = 1640; + LastUpgradeCheck = 2630; TargetAttributes = { 862D28E62E2D51E100F9B6FE = { CreatedOnToolsVersion = 16.4; @@ -118,8 +112,8 @@ mainGroup = 862D28DE2E2D51E100F9B6FE; minimizedProjectReferenceProxies = 1; packageReferences = ( - 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */, C1C5F2D42FF0000000000001 /* XCLocalSwiftPackageReference "../../MathEditor/MathKeyboardSwiftUI" */, + 86E17B802F7569EF0079CEC2 /* XCLocalSwiftPackageReference "../MathEditorSwift" */, ); preferredProjectObjectVersion = 77; productRefGroup = 862D28E82E2D51E100F9B6FE /* Products */; @@ -186,6 +180,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = BT9948YGAL; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -209,6 +204,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -248,6 +244,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = BT9948YGAL; ENABLE_NS_ASSERTIONS = NO; @@ -264,6 +261,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; @@ -276,9 +274,12 @@ CODE_SIGN_ENTITLEMENTS = MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = BT9948YGAL; + ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -315,9 +316,12 @@ CODE_SIGN_ENTITLEMENTS = MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = BT9948YGAL; + ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -370,9 +374,9 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */ = { + 86E17B802F7569EF0079CEC2 /* XCLocalSwiftPackageReference "../MathEditorSwift" */ = { isa = XCLocalSwiftPackageReference; - relativePath = ../../MathEditor; + relativePath = ../MathEditorSwift; }; C1C5F2D42FF0000000000001 /* XCLocalSwiftPackageReference "../../MathEditor/MathKeyboardSwiftUI" */ = { isa = XCLocalSwiftPackageReference; @@ -381,18 +385,9 @@ /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 862D28F72E2D525900F9B6FE /* MathEditor */ = { + 86E17B812F7569EF0079CEC2 /* MathEditorSwift */ = { isa = XCSwiftPackageProductDependency; - productName = MathEditor; - }; - 862D28FA2E2D527100F9B6FE /* MathEditor */ = { - isa = XCSwiftPackageProductDependency; - productName = MathEditor; - }; - C1489ED42F6CB33A00AB2993 /* MathKeyboard */ = { - isa = XCSwiftPackageProductDependency; - package = 862D28F92E2D527100F9B6FE /* XCLocalSwiftPackageReference "../../MathEditor" */; - productName = MathKeyboard; + productName = MathEditorSwift; }; C1DAAE002F70231100E4E983 /* MathKeyboardSwiftUI */ = { isa = XCSwiftPackageProductDependency; diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index f82a0bd..0c61807 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -1,6 +1,5 @@ // Copyright © 2025 Snap, Inc. All rights reserved. -import MathEditor import MathEditorSwift import SwiftUI @@ -17,20 +16,19 @@ struct ContentView: View { } #if os(iOS) - import MathKeyboard import MathKeyboardSwiftUI struct MathEditorView: UIViewRepresentable { - typealias UIViewType = MTEditableMathLabel + typealias UIViewType = MTEditableMathLabelSwift - func makeUIView(context: Context) -> MTEditableMathLabel { - let mathLabel = MTEditableMathLabel() + func makeUIView(context: Context) -> MTEditableMathLabelSwift { + let mathLabel = MTEditableMathLabelSwift() mathLabel.backgroundColor = .clear mathLabel.keyboard = MTMathKeyboardSwiftUIRootView.sharedInstance() return mathLabel } - func updateUIView(_ uiView: MTEditableMathLabel, context: Context) { + func updateUIView(_ uiView: MTEditableMathLabelSwift, context: Context) { } } diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements index f2ef3ae..0c67376 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements @@ -1,10 +1,5 @@ - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - + diff --git a/MathKeyboardSwiftUI/Package.swift b/MathKeyboardSwiftUI/Package.swift index 1bab276..2b4f07b 100644 --- a/MathKeyboardSwiftUI/Package.swift +++ b/MathKeyboardSwiftUI/Package.swift @@ -13,13 +13,13 @@ let package = Package( ) ], dependencies: [ - .package(path: "..") + .package(path: "../MathEditorSwift") ], targets: [ .target( name: "MathKeyboardSwiftUI", dependencies: [ - .product(name: "MathEditor", package: "MathEditor") + "MathEditorSwift" ], path: "Sources/MathKeyboardSwiftUI", resources: [.process("Resources")], diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift index 813ebe8..db3b18c 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/FunctionsKeyboardView.swift @@ -1,6 +1,7 @@ import Foundation -import MathEditor +import MathEditorSwift import SwiftUI +import iosMath struct FunctionsKeyboardView: View { let state: KeyboardState diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift index 4313c8f..b1c2430 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MainKeyboardView.swift @@ -1,6 +1,7 @@ import Foundation -import MathEditor +import MathEditorSwift import SwiftUI +import iosMath struct MainKeyboardView: View { let backgroundImageName: String diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index 0f12e73..e62f15e 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -1,10 +1,10 @@ #if os(iOS) - import MathEditor + import MathEditorSwift import SwiftUI import UIKit - public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboard, UIInputViewAudioFeedback + public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboardSwift, UIInputViewAudioFeedback { private static let defaultTab: KeyboardTab = .numbers private static let shared = MTMathKeyboardSwiftUIRootView() @@ -77,12 +77,12 @@ set { updateState { $0.radicalHighlighted = newValue } } } - public func startedEditing(_ label: (any UIView & UIKeyInput)!) { + public func startedEditing(_ label: any UIView & UIKeyInput) { textInput = label updateRootView() } - public func finishedEditing(_ label: (any UIView & UIKeyInput)!) { + public func finishedEditing(_ label: any UIView & UIKeyInput) { if textInput === label { textInput = nil updateRootView() From c6cdeb5781a9ddaa18751aca932d7ab2d75bf6f0 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 14:23:07 +0000 Subject: [PATCH 111/133] Remove compat functions in favor of categories --- .../Internal/MTCaretViewSwift.swift | 24 ++++--------------- .../MTEditableMathLabelSwift.swift | 18 +++----------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift index 6c3e348..10b566e 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift @@ -28,7 +28,7 @@ private final class MTCaretHandleSwift: MTView { var color: MTColor? { didSet { baseColor = color?.withAlphaComponent(0.7) - setNeedsDisplayCompat() + setNeedsDisplay() } } @@ -59,12 +59,12 @@ private final class MTCaretHandleSwift: MTView { private func interactionBegan() { baseColor = baseColor?.withAlphaComponent(1.0) - setNeedsDisplayCompat() + setNeedsDisplay() } private func interactionEnded() { baseColor = baseColor?.withAlphaComponent(0.6) - setNeedsDisplayCompat() + setNeedsDisplay() } private func handleDrag(localPoint: CGPoint) { @@ -153,14 +153,6 @@ private final class MTCaretHandleSwift: MTView { return hitArea().contains(localPoint) ? self : nil } #endif - - private func setNeedsDisplayCompat() { - #if canImport(UIKit) - setNeedsDisplay() - #else - needsDisplay = true - #endif - } } public final class MTCaretViewSwift: MTView { @@ -208,7 +200,7 @@ public final class MTCaretViewSwift: MTView { func setFontSize(_ fontSize: CGFloat) { scale = fontSize / caretFontSize - setNeedsLayoutCompat() + setNeedsLayout() } func showHandle(_ show: Bool) { @@ -293,12 +285,4 @@ public final class MTCaretViewSwift: MTView { hitTestOutsideBounds(point) } #endif - - private func setNeedsLayoutCompat() { - #if canImport(UIKit) - setNeedsLayout() - #else - needsLayout = true - #endif - } } diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index 8285b54..b42894b 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -125,7 +125,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { layoutLabelIfNeeded() guard let displayList = label.displayList else { return } displayList.highlightCharacter(at: index, color: highlightColor) - setNeedsDisplayCompat() + setNeedsDisplay() } @objc public func clearHighlights() { @@ -441,14 +441,14 @@ extension MTEditableMathLabelSwift { caretView.setPosition(caretPosition.applying(flipTransform)) if caretView.superview == nil { addSubview(caretView) - setNeedsDisplayCompat() + setNeedsDisplay() } // when a caret is displayed, the X symbol should be as well cancelImage?.isHidden = false // Set up a timer to "blink" the caret. caretView.delayBlink() - setLabelNeedsLayoutCompat() + label.setNeedsLayout() } fileprivate func setKeyboardMode() { @@ -798,16 +798,4 @@ extension MTEditableMathLabelSwift { label.layoutSubtreeIfNeeded() #endif } - - fileprivate func setNeedsDisplayCompat() { - #if canImport(UIKit) - setNeedsDisplay() - #else - needsDisplay = true - #endif - } - - fileprivate func setLabelNeedsLayoutCompat() { - label.setNeedsLayout() - } } From 44e564335ef795f7659a3a7939558e39ec3e6407 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 14:27:17 +0000 Subject: [PATCH 112/133] MTCaretHandlSwift -> CaretHandle --- .../Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift | 6 +++--- MathEditorSwift/todo.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift index 10b566e..7c62176 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift @@ -22,7 +22,7 @@ private func caretHeight() -> CGFloat { caretAscent + caretDescent } -private final class MTCaretHandleSwift: MTView { +private final class CaretHandle: MTView { weak var label: MTEditableMathLabelSwift? var color: MTColor? { @@ -165,12 +165,12 @@ public final class MTCaretViewSwift: MTView { private var blinkTimer: Timer? private let blinker = MTView(frame: .zero) - private let handle: MTCaretHandleSwift + private let handle: CaretHandle private var scale: CGFloat init(editor: MTEditableMathLabelSwift) { scale = editor.fontSize / caretFontSize - handle = MTCaretHandleSwift( + handle = CaretHandle( frame: CGRect( x: 0, y: 0, diff --git a/MathEditorSwift/todo.md b/MathEditorSwift/todo.md index 93b0f9b..40b435a 100644 --- a/MathEditorSwift/todo.md +++ b/MathEditorSwift/todo.md @@ -2,7 +2,7 @@ ## MTCaretView 1. - [ ] No need for baseColor vs color -2. - [ ] setNeedsDisplayCompatk +2. - [x] setNeedsDisplayCompat 3. - [ ] handleDrag convert is different implementation 4. - [ ] no mouse cancelled 5. - [ ] can caretColor be non-optional From 4a4c9b7c68d4e8cb4fb74e09ace2d12d0b3706d0 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 14:32:58 +0000 Subject: [PATCH 113/133] MTCaretViewSwift -> MTCaretView --- .../Sources/MathEditorSwift/Internal/MTCancelView.swift | 3 +-- .../Internal/{MTCaretViewSwift.swift => MTCaretView.swift} | 2 +- .../Sources/MathEditorSwift/MTEditableMathLabelSwift.swift | 4 ++-- MathEditorSwift/todo.md | 6 +++--- 4 files changed, 7 insertions(+), 8 deletions(-) rename MathEditorSwift/Sources/MathEditorSwift/Internal/{MTCaretViewSwift.swift => MTCaretView.swift} (99%) diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift index 11e7d64..a09403c 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift @@ -1,6 +1,5 @@ import Foundation -@objc public final class MTCancelView: MTView { private let imageView: MTImageView @@ -18,7 +17,7 @@ public final class MTCancelView: MTView { imageView.contentTintColor = .secondaryLabelColor #endif - super.init(frame: .zero) + super.init() addSubview(imageView) imageView.pinToSuperview() diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretView.swift similarity index 99% rename from MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift rename to MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretView.swift index 7c62176..c225a85 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretViewSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretView.swift @@ -155,7 +155,7 @@ private final class CaretHandle: MTView { #endif } -public final class MTCaretViewSwift: MTView { +final class MTCaretView: MTView { public var caretColor: MTColor? { didSet { handle.color = caretColor diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index b42894b..d5f2516 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -76,7 +76,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { } @objc public private(set) var cancelImage: MTCancelView? - @objc public private(set) var caretView: MTCaretViewSwift! + @objc private(set) var caretView: MTCaretView! public weak var delegate: MTEditableMathLabelSwiftDelegate? public weak var keyboard: (MTView & MTMathKeyboardSwift)? @@ -340,7 +340,7 @@ extension MTEditableMathLabelSwift { let transform = CGAffineTransform(translationX: 0, y: bounds.size.height) flipTransform = CGAffineTransform(scaleX: 1, y: -1).concatenating(transform) - caretView = MTCaretViewSwift(editor: self) + caretView = MTCaretView(editor: self) caretView.caretColor = MTColor(white: 0.1, alpha: 1.0) highlightColor = MTColor.systemRed diff --git a/MathEditorSwift/todo.md b/MathEditorSwift/todo.md index 40b435a..d3d12f0 100644 --- a/MathEditorSwift/todo.md +++ b/MathEditorSwift/todo.md @@ -8,11 +8,11 @@ 5. - [ ] can caretColor be non-optional 6. - [ ] Can caret handle initialized with label 7. - [ ] is startBlinkingIfNeeded correct? -8. - [ ] Rename MTCaretHandleSwift to CaretHandle +8. - [x] Rename MTCaretHandleSwift to CaretHandle ## MTEditableMathlabelSwift -1. - [ ] MTKeyInputSwift should be alias to UIKeyInput +1. - [x] MTKeyInputSwift should be alias to UIKeyInput 2. - [x] Bring comments back 3. - [ ] highlightColor is different in `initialize` 4. - [ ] layoutLabelIfNeeded should be the same as objc @@ -22,7 +22,7 @@ ### Responder -1. - [ ] Should not extend UIKeyInput +1. - [x] Should not extend UIKeyInput 2. - [ ] reuse becomeFirstResponder on both 3. - [ ] Reuse resignFirstResponder on both 4. - [ ] From d14d299a4b32847c831523bf964f304201603c13 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 15:26:50 +0000 Subject: [PATCH 114/133] DummyTextInputHandler --- .../Internal/DummyTextInputHandler.swift | 27 +++++ .../Internal/MTCancelView.swift | 2 +- ...MTEditableMathLabelSwift+UITextInput.swift | 107 ++---------------- .../MTEditableMathLabelSwift.swift | 2 + .../Sources/MathEditorSwift/MTKeyInput.swift | 16 ++- .../MTMathKeyboardSwiftUIRootView.swift | 3 +- Package.swift | 2 +- 7 files changed, 51 insertions(+), 108 deletions(-) create mode 100644 MathEditorSwift/Sources/MathEditorSwift/Internal/DummyTextInputHandler.swift diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/DummyTextInputHandler.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/DummyTextInputHandler.swift new file mode 100644 index 0000000..f232884 --- /dev/null +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/DummyTextInputHandler.swift @@ -0,0 +1,27 @@ +// +// MTTextInputHandler.swift +// MathEditorSwift +// +// Created by Madiyar Aitbayev on 26/03/2026. +// + +#if canImport(UIKit) + + import UIKit + + struct DummyTextInputHandler { + var selectedTextRange: UITextRange? + var markedTextRange: UITextRange? + var markedTextStyle: [NSAttributedString.Key: Any]? + var beginningOfDocument = UITextPosition() + var endOfDocument = UITextPosition() + var inputDelegate: (any UITextInputDelegate)? + var tokenizer: UITextInputTokenizer = UITextInputStringTokenizer() + } + +#else // canImport(UIKit) + + struct DummyTextInputHandler { + } + +#endif // canImport(UIKit) diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift index a09403c..6e01399 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCancelView.swift @@ -17,7 +17,7 @@ public final class MTCancelView: MTView { imageView.contentTintColor = .secondaryLabelColor #endif - super.init() + super.init(frame: .zero) addSubview(imageView) imageView.pinToSuperview() diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift index 751f717..d1dc6c2 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift @@ -6,132 +6,49 @@ import Foundation // These are blank just to get a UITextInput implementation, to fix the dictation button bug. // Proposed fix from: http://stackoverflow.com/questions/20980898/work-around-for-dictation-custom-text-view-bug - private final class MTTextInputPosition: UITextPosition {} - - private final class MTTextInputRange: UITextRange { - private let internalStart = MTTextInputPosition() - private let internalEnd = MTTextInputPosition() - - override var start: UITextPosition { internalStart } - override var end: UITextPosition { internalEnd } - override var isEmpty: Bool { true } - } - - private final class WeakTextInputDelegateBox { - weak var value: UITextInputDelegate? - - init(_ value: UITextInputDelegate?) { - self.value = value - } - } - - private enum MTTextInputAssociatedKeys { - static var selectedTextRange = "mt_selectedTextRange" - static var inputDelegate = "mt_inputDelegate" - static var markedTextRange = "mt_markedTextRange" - static var markedTextStyle = "mt_markedTextStyle" - static var tokenizer = "mt_tokenizer" - static var beginningOfDocument = "mt_beginningOfDocument" - static var endOfDocument = "mt_endOfDocument" - } extension MTEditableMathLabelSwift: UITextInput { public var selectedTextRange: UITextRange? { get { - objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.selectedTextRange) as? UITextRange + textInputHandler.selectedTextRange } set { - objc_setAssociatedObject( - self, - &MTTextInputAssociatedKeys.selectedTextRange, - newValue, - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) + textInputHandler.selectedTextRange = newValue } } public var inputDelegate: UITextInputDelegate? { get { - (objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.inputDelegate) - as? WeakTextInputDelegateBox)?.value + textInputHandler.inputDelegate } set { - objc_setAssociatedObject( - self, - &MTTextInputAssociatedKeys.inputDelegate, - WeakTextInputDelegateBox(newValue), - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) + textInputHandler.inputDelegate = newValue } } public var markedTextRange: UITextRange? { - objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.markedTextRange) as? UITextRange + textInputHandler.markedTextRange } public var markedTextStyle: [NSAttributedString.Key: Any]? { get { - objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.markedTextStyle) - as? [NSAttributedString.Key: Any] + textInputHandler.markedTextStyle } set { - objc_setAssociatedObject( - self, - &MTTextInputAssociatedKeys.markedTextStyle, - newValue, - .OBJC_ASSOCIATION_COPY_NONATOMIC - ) + textInputHandler.markedTextStyle = newValue } } public var beginningOfDocument: UITextPosition { - if let position = objc_getAssociatedObject( - self, - &MTTextInputAssociatedKeys.beginningOfDocument - ) as? UITextPosition { - return position - } - let position = MTTextInputPosition() - objc_setAssociatedObject( - self, - &MTTextInputAssociatedKeys.beginningOfDocument, - position, - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) - return position + textInputHandler.beginningOfDocument } public var endOfDocument: UITextPosition { - if let position = objc_getAssociatedObject( - self, - &MTTextInputAssociatedKeys.endOfDocument - ) as? UITextPosition { - return position - } - let position = MTTextInputPosition() - objc_setAssociatedObject( - self, - &MTTextInputAssociatedKeys.endOfDocument, - position, - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) - return position + textInputHandler.endOfDocument } public var tokenizer: UITextInputTokenizer { - if let tokenizer = objc_getAssociatedObject(self, &MTTextInputAssociatedKeys.tokenizer) - as? UITextInputTokenizer - { - return tokenizer - } - let tokenizer = UITextInputStringTokenizer(textInput: self) - objc_setAssociatedObject( - self, - &MTTextInputAssociatedKeys.tokenizer, - tokenizer, - .OBJC_ASSOCIATION_RETAIN_NONATOMIC - ) - return tokenizer + textInputHandler.tokenizer } public func baseWritingDirection( @@ -186,9 +103,7 @@ import Foundation public func insertDictationResult(_ dictationResult: [UIDictationPhrase]) {} - public func insertDictationResultPlaceholder() -> Any { - MTTextInputRange() - } + public var insertDictationResultPlaceholder: Any { 0 } public func offset(from: UITextPosition, to toPosition: UITextPosition) -> Int { 0 diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index d5f2516..ea54cf9 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -99,6 +99,8 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { private var insertionIndex: MTMathListIndex? private var flipTransform = CGAffineTransform.identity + var textInputHandler = DummyTextInputHandler() + public override init(frame: CGRect) { super.init(frame: frame) initialize() diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift b/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift index 66661b9..a1ad8a1 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift @@ -5,15 +5,13 @@ // Created by Madiyar Aitbayev on 26/03/2026. // - #if canImport(UIKit) -import UIKit -public typealias MTKeyInput = UIKeyInput + import UIKit + public typealias MTKeyInput = UIKeyInput #else -public protocol MTKeyInput { - var hasText: Bool { get } - func insertText(_ text: String) - func deleteBackward() -} + public protocol MTKeyInput { + var hasText: Bool { get } + func insertText(_ text: String) + func deleteBackward() + } #endif - diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index e62f15e..5261454 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -4,7 +4,8 @@ import SwiftUI import UIKit - public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboardSwift, UIInputViewAudioFeedback + public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboardSwift, + UIInputViewAudioFeedback { private static let defaultTab: KeyboardTab = .numbers private static let shared = MTMathKeyboardSwiftUIRootView() diff --git a/Package.swift b/Package.swift index 974cc5b..cec4182 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master"), - .package(path: "./MathEditorSwift") + .package(path: "./MathEditorSwift"), ], targets: [ .target( From 7bd875bdc1ba27f8c40fe327c86dae18ef816d3a Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 15:32:39 +0000 Subject: [PATCH 115/133] Reuse Responder methods --- .../MTEditableMathLabelSwift+Responder.swift | 56 ++++++++----------- MathEditorSwift/todo.md | 4 +- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift index 118451e..f133f10 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift @@ -2,50 +2,42 @@ import Foundation #if canImport(UIKit) import UIKit +#endif +#if canImport(AppKit) + import AppKit +#endif + +extension MTEditableMathLabelSwift { + public override func becomeFirstResponder() -> Bool { + let didBecome = super.becomeFirstResponder() + if didBecome { + doBecomeFirstResponder() + } + return didBecome + } + + public override func resignFirstResponder() -> Bool { + guard isFirstResponder else { return true } + let didResign = super.resignFirstResponder() + doResignFirstResponder() + return didResign + } +} +#if canImport(UIKit) extension MTEditableMathLabelSwift { public override var inputView: UIView? { keyboard as? UIView } public override var canBecomeFirstResponder: Bool { true } - - public override func becomeFirstResponder() -> Bool { - let didBecome = super.becomeFirstResponder() - if didBecome { - doBecomeFirstResponder() - } - return didBecome - } - - public override func resignFirstResponder() -> Bool { - guard isFirstResponder else { return true } - let didResign = super.resignFirstResponder() - doResignFirstResponder() - return didResign - } } -#elseif canImport(AppKit) - import AppKit +#endif +#if canImport(AppKit) extension MTEditableMathLabelSwift { public override var acceptsFirstResponder: Bool { true } - public override func becomeFirstResponder() -> Bool { - let didBecome = super.becomeFirstResponder() - if didBecome { - doBecomeFirstResponder() - } - return didBecome - } - - public override func resignFirstResponder() -> Bool { - guard window?.firstResponder === self else { return true } - let didResign = super.resignFirstResponder() - doResignFirstResponder() - return didResign - } - public override func keyDown(with event: NSEvent) { // interpretKeyEvents feeds the event into the input system, // which calls insertText: or deleteBackward: as appropriate. diff --git a/MathEditorSwift/todo.md b/MathEditorSwift/todo.md index d3d12f0..5ca37f6 100644 --- a/MathEditorSwift/todo.md +++ b/MathEditorSwift/todo.md @@ -23,6 +23,6 @@ ### Responder 1. - [x] Should not extend UIKeyInput -2. - [ ] reuse becomeFirstResponder on both -3. - [ ] Reuse resignFirstResponder on both +2. - [x] reuse becomeFirstResponder on both +3. - [x] Reuse resignFirstResponder on both 4. - [ ] From a3e7977e8f7fa75bc6f328538c043ac3bc2004cc Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 17:48:21 +0000 Subject: [PATCH 116/133] Improve MTCaretView --- .../Internal/MTCaretView.swift | 97 +++++++++++-------- .../Internal/MTView/NSColor+label.swift | 17 ++++ .../MTEditableMathLabelSwift.swift | 2 +- MathEditorSwift/todo.md | 17 ++-- 4 files changed, 80 insertions(+), 53 deletions(-) create mode 100644 MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSColor+label.swift diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretView.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretView.swift index c225a85..06a5b37 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretView.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTCaretView.swift @@ -1,4 +1,5 @@ import Foundation +import iosMath #if canImport(UIKit) import UIKit @@ -25,19 +26,28 @@ private func caretHeight() -> CGFloat { private final class CaretHandle: MTView { weak var label: MTEditableMathLabelSwift? - var color: MTColor? { - didSet { - baseColor = color?.withAlphaComponent(0.7) - setNeedsDisplay() - } + var color: MTColor = MTColor.label { + didSet { setNeedsDisplay() } } private var path = MTBezierPath() - private var baseColor: MTColor? + private var isInteracting = false + + private var hitArea: CGRect { + // Create a hit area around the center. + let size = bounds.size + return CGRect( + x: (size.width - caretHandleHitAreaSize) / 2, + y: (size.height - caretHandleHitAreaSize) / 2, + width: caretHandleHitAreaSize, + height: caretHandleHitAreaSize + ) + } override init(frame: CGRect) { super.init(frame: frame) path = createHandlePath() + backgroundColor = .clear } @available(*, unavailable) @@ -58,40 +68,32 @@ private final class CaretHandle: MTView { } private func interactionBegan() { - baseColor = baseColor?.withAlphaComponent(1.0) + isInteracting = true setNeedsDisplay() } private func interactionEnded() { - baseColor = baseColor?.withAlphaComponent(0.6) + isInteracting = false setNeedsDisplay() } private func handleDrag(localPoint: CGPoint) { - let caretPoint = CGPoint(x: localPoint.x, y: localPoint.y - frame.origin.y) guard let label else { return } - let labelPoint = convert(caretPoint, to: label) + let caretPoint = CGPoint(x: localPoint.x, y: localPoint.y - frame.origin.y) + let labelPoint = label.convert(caretPoint, from: self) // puts the point at the top to the top of the current caret label.moveCaret(to: labelPoint) } - private func hitArea() -> CGRect { - // Create a hit area around the center. - let size = bounds.size - return CGRect( - x: (size.width - caretHandleHitAreaSize) / 2, - y: (size.height - caretHandleHitAreaSize) / 2, - width: caretHandleHitAreaSize, - height: caretHandleHitAreaSize - ) + public override func draw(_ rect: CGRect) { + let drawColor = color.withAlphaComponent(isInteracting ? 1.0 : 0.6) + drawColor.setFill() + path.fill() } +} - #if canImport(UIKit) - public override func draw(_ rect: CGRect) { - baseColor?.setFill() - path.fill() - } - +#if canImport(UIKit) + extension CaretHandle { public override func layoutSubviews() { super.layoutSubviews() path = createHandlePath() @@ -106,7 +108,6 @@ private final class CaretHandle: MTView { } public override func touchesMoved(_ touches: Set, with event: UIEvent?) { - // From apple documentation guard let touch = touches.first else { return } handleDrag(localPoint: touch.location(in: self)) } @@ -116,15 +117,14 @@ private final class CaretHandle: MTView { } public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - hitArea().contains(point) + self.hitArea.contains(point) } - #else - public override var isFlipped: Bool { true } + } +#endif // canImport(UIKit) - public override func draw(_ dirtyRect: NSRect) { - baseColor?.setFill() - path.fill() - } +#if canImport(AppKit) + extension CaretHandle { + public override var isFlipped: Bool { true } public override func layout() { super.layout() @@ -147,16 +147,20 @@ private final class CaretHandle: MTView { interactionEnded() } + public override func mouseCancelled(with event: NSEvent) { + interactionEnded() + } + public override func hitTest(_ point: NSPoint) -> NSView? { guard !isHidden else { return nil } let localPoint = convert(point, from: superview) - return hitArea().contains(localPoint) ? self : nil + return hitArea.contains(localPoint) ? self : nil } - #endif -} + } +#endif // canImport(AppKit) final class MTCaretView: MTView { - public var caretColor: MTColor? { + public var caretColor: MTColor = MTColor.label { didSet { handle.color = caretColor blinker.backgroundColor = caretColor @@ -166,7 +170,7 @@ final class MTCaretView: MTView { private var blinkTimer: Timer? private let blinker = MTView(frame: .zero) private let handle: CaretHandle - private var scale: CGFloat + private var scale: Double init(editor: MTEditableMathLabelSwift) { scale = editor.fontSize / caretFontSize @@ -182,7 +186,7 @@ final class MTCaretView: MTView { blinker.backgroundColor = caretColor addSubview(blinker) - handle.backgroundColor = .clear + handle.color = caretColor handle.isHidden = true handle.label = editor addSubview(handle) @@ -246,9 +250,12 @@ final class MTCaretView: MTView { deinit { blinkTimer?.invalidate() + blinkTimer = nil } +} - #if canImport(UIKit) +#if canImport(UIKit) + extension MTCaretView { public override func didMoveToSuperview() { super.didMoveToSuperview() // UIView didMoveToSuperview override to set up blink timers after caret view created in superview. @@ -267,7 +274,11 @@ final class MTCaretView: MTView { super.layoutSubviews() doLayout() } - #else + } +#endif // canImport(UIKit) + +#if canImport(AppKit) + extension MTCaretView { public override var isFlipped: Bool { true } public override func viewDidMoveToSuperview() { @@ -284,5 +295,5 @@ final class MTCaretView: MTView { public override func hitTest(_ point: NSPoint) -> NSView? { hitTestOutsideBounds(point) } - #endif -} + } +#endif // canImport(AppKit) diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSColor+label.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSColor+label.swift new file mode 100644 index 0000000..bc7cf97 --- /dev/null +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/NSColor+label.swift @@ -0,0 +1,17 @@ +// +// NSColor+labelColor.swift +// MathEditorSwift +// +// Created by Madiyar Aitbayev on 26/03/2026. +// + +#if canImport(AppKit) + import AppKit + + extension NSColor { + static var label: NSColor { + NSColor.labelColor + } + } + +#endif // canImport(AppKit) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index ea54cf9..4528c64 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -70,7 +70,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { set { label.textColor = newValue ?? label.textColor } } - @objc public var caretColor: MTColor? { + @objc public var caretColor: MTColor { get { caretView.caretColor } set { caretView.caretColor = newValue } } diff --git a/MathEditorSwift/todo.md b/MathEditorSwift/todo.md index 5ca37f6..8e87e28 100644 --- a/MathEditorSwift/todo.md +++ b/MathEditorSwift/todo.md @@ -1,13 +1,13 @@ ## MTCaretView -1. - [ ] No need for baseColor vs color +1. - [x] No need for baseColor vs color 2. - [x] setNeedsDisplayCompat -3. - [ ] handleDrag convert is different implementation -4. - [ ] no mouse cancelled -5. - [ ] can caretColor be non-optional -6. - [ ] Can caret handle initialized with label -7. - [ ] is startBlinkingIfNeeded correct? +3. - [x] handleDrag convert is different implementation +4. - [x] no mouse cancelled +5. - [x] can caretColor be non-optional +6. - [x] Can caret handle initialized with label +7. - [x] is startBlinkingIfNeeded correct? 8. - [x] Rename MTCaretHandleSwift to CaretHandle ## MTEditableMathlabelSwift @@ -16,8 +16,8 @@ 2. - [x] Bring comments back 3. - [ ] highlightColor is different in `initialize` 4. - [ ] layoutLabelIfNeeded should be the same as objc -5. - [ ] setNeedsDisplayCompat should be changed -6. - [ ] setLabelNeedsDisplayCompat should be changed +5. - [x] setNeedsDisplayCompat should be changed +6. - [x] setLabelNeedsDisplayCompat should be changed 7. - [ ] getIndexAfterSpecialStructure: is next should unwrapped? ### Responder @@ -25,4 +25,3 @@ 1. - [x] Should not extend UIKeyInput 2. - [x] reuse becomeFirstResponder on both 3. - [x] Reuse resignFirstResponder on both -4. - [ ] From 9f7b3a770aeed8fd2926d8b4f3a15c42e1e2fe51 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 17:51:50 +0000 Subject: [PATCH 117/133] labelLayoutIfNeeded --- .../MTEditableMathLabelSwift.swift | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index 4528c64..5dc5071 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -124,7 +124,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { @objc(highlightCharacterAtIndex:) public func highlightCharacter(at index: MTMathListIndex) { - layoutLabelIfNeeded() + label.layoutIfNeeded() guard let displayList = label.displayList else { return } displayList.highlightCharacter(at: index, color: highlightColor) setNeedsDisplay() @@ -178,7 +178,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { // update the flip transform let transform = CGAffineTransform(translationX: 0, y: bounds.size.height) flipTransform = CGAffineTransform(scaleX: 1, y: -1).concatenating(transform) - layoutLabelIfNeeded() + label.layoutIfNeeded() insertionPointChanged() } @@ -305,7 +305,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { @objc(closestIndexToPoint:) public func closestIndex(to point: CGPoint) -> MTMathListIndex? { - layoutLabelIfNeeded() + label.layoutIfNeeded() // no mathlist, so can't figure it out. guard let displayList = label.displayList else { return nil } return displayList.closestIndex(to: convert(point, to: label)) @@ -313,7 +313,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { @objc(caretRectForIndex:) public func caretRect(for index: MTMathListIndex) -> CGPoint { - layoutLabelIfNeeded() + label.layoutIfNeeded() // no mathlist so we can't figure it out. guard let displayList = label.displayList else { return .zero } return displayList.caretPosition(for: index) @@ -792,12 +792,4 @@ extension MTEditableMathLabelSwift { break } } - - fileprivate func layoutLabelIfNeeded() { - #if canImport(UIKit) - label.layoutIfNeeded() - #else - label.layoutSubtreeIfNeeded() - #endif - } } From b5fc8075ee90741a738b46c832e94dc8db302a1b Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 17:53:41 +0000 Subject: [PATCH 118/133] remove unused --- .../Sources/MathEditorSwift/MTEditableMathLabelSwift.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index 5dc5071..77d34c2 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -1,12 +1,6 @@ import Foundation import iosMath -#if canImport(UIKit) - import UIKit -#elseif canImport(AppKit) - import AppKit -#endif - private let greekLowerStart: UInt32 = 0x03B1 private let greekLowerEnd: UInt32 = 0x03C9 private let greekCapitalStart: UInt32 = 0x0391 From 952747086c2cf2dd44a3e8b8d68bc5de1fb630fd Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 26 Mar 2026 17:59:51 +0000 Subject: [PATCH 119/133] Add Swift Testing port of display editing tests --- MathEditorSwift/Package.swift | 4 + .../MTDisplayEditingTests.swift | 191 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift diff --git a/MathEditorSwift/Package.swift b/MathEditorSwift/Package.swift index 0d925d3..e04ad66 100644 --- a/MathEditorSwift/Package.swift +++ b/MathEditorSwift/Package.swift @@ -18,6 +18,10 @@ let package = Package( .target( name: "MathEditorSwift", dependencies: ["iosMath"] + ), + .testTarget( + name: "MathEditorSwiftTests", + dependencies: ["MathEditorSwift"] ) ] ) diff --git a/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift new file mode 100644 index 0000000..beb7b9c --- /dev/null +++ b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift @@ -0,0 +1,191 @@ +import CoreGraphics +import Testing +@testable import MathEditorSwift +import iosMath + +struct MTDisplayEditingTests { + private let font: MTFont = MTFontManager().latinModernFont(withSize: 20) + + private struct ClosestIndexCase { + let point: CGPoint + let expected: MTMathListIndex + } + + private func assertClosestIndex( + expression: String, + cases: [ClosestIndexCase] + ) { + let mathList = MTMathListBuilder.build(fromString: expression) + let displayList = MTTypesetter.createLine(forMathList: mathList, font: font, style: kMTLineStyleDisplay) + + for testCase in cases { + let actual = displayList?.closestIndex(to: testCase.point) + #expect(actual?.isEqual(testCase.expected) == true, + "Index \(String(describing: actual)) does not match \(testCase.expected) for point \(testCase.point)") + } + } + + @Test("closest index for fraction") + func closestPointFraction() { + assertClosestIndex(expression: "\\frac{3}{2}", cases: fractionCases) + } + + @Test("closest index for regular expression") + func closestPointRegular() { + assertClosestIndex(expression: "4+2", cases: regularCases) + } + + @Test("closest index for regular plus fraction") + func closestPointRegularPlusFraction() { + assertClosestIndex(expression: "1+\\frac{3}{2}", cases: regularPlusFractionCases) + } + + @Test("closest index for fraction plus regular") + func closestPointFractionPlusRegular() { + assertClosestIndex(expression: "\\frac{3}{2}+1", cases: fractionPlusRegularCases) + } + + @Test("closest index for exponent") + func closestPointExponent() { + assertClosestIndex(expression: "2^3", cases: exponentCases) + } +} + +private let fractionCases: [MTDisplayEditingTests.ClosestIndexCase] = [ + .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: -20), expected: .level0Index(0)), + .init(point: CGPoint(x: -2.5, y: 8), expected: .level0Index(0)), + .init(point: CGPoint(x: -2.5, y: 0), expected: .level0Index(0)), + .init(point: CGPoint(x: -2.5, y: 40), expected: .level0Index(0)), + .init(point: CGPoint(x: -2.5, y: -20), expected: .level0Index(0)), + .init(point: CGPoint(x: -1, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: -1, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: -1, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: -1, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 3, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 3, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 3, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 3, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 7, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 7, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 7, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 7, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 11, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 11, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 11, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 11, y: -20), expected: .level0Index(1)), + .init(point: CGPoint(x: 12.5, y: 8), expected: .level0Index(1)), + .init(point: CGPoint(x: 12.5, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 12.5, y: 40), expected: .level0Index(1)), + .init(point: CGPoint(x: 12.5, y: -20), expected: .level0Index(1)), + .init(point: CGPoint(x: 20, y: 8), expected: .level0Index(1)), + .init(point: CGPoint(x: 20, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 20, y: 40), expected: .level0Index(1)), + .init(point: CGPoint(x: 20, y: -20), expected: .level0Index(1)), +] + +private let regularCases: [MTDisplayEditingTests.ClosestIndexCase] = [ + .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: -20), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: 0), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: 8), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: 40), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: -20), expected: .level0Index(0)), + .init(point: CGPoint(x: 10, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 10, y: 8), expected: .level0Index(1)), + .init(point: CGPoint(x: 10, y: 40), expected: .level0Index(1)), + .init(point: CGPoint(x: 10, y: -20), expected: .level0Index(1)), + .init(point: CGPoint(x: 15, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 15, y: 8), expected: .level0Index(1)), + .init(point: CGPoint(x: 15, y: 40), expected: .level0Index(1)), + .init(point: CGPoint(x: 15, y: -20), expected: .level0Index(1)), + .init(point: CGPoint(x: 25, y: 0), expected: .level0Index(2)), + .init(point: CGPoint(x: 25, y: 8), expected: .level0Index(2)), + .init(point: CGPoint(x: 25, y: 40), expected: .level0Index(2)), + .init(point: CGPoint(x: 25, y: -20), expected: .level0Index(2)), + .init(point: CGPoint(x: 35, y: 0), expected: .level0Index(2)), + .init(point: CGPoint(x: 35, y: 8), expected: .level0Index(2)), + .init(point: CGPoint(x: 35, y: 40), expected: .level0Index(2)), + .init(point: CGPoint(x: 35, y: -20), expected: .level0Index(2)), + .init(point: CGPoint(x: 45, y: 0), expected: .level0Index(3)), + .init(point: CGPoint(x: 45, y: 8), expected: .level0Index(3)), + .init(point: CGPoint(x: 45, y: 40), expected: .level0Index(3)), + .init(point: CGPoint(x: 45, y: -20), expected: .level0Index(3)), + .init(point: CGPoint(x: 55, y: 0), expected: .level0Index(3)), + .init(point: CGPoint(x: 55, y: 8), expected: .level0Index(3)), + .init(point: CGPoint(x: 55, y: 40), expected: .level0Index(3)), + .init(point: CGPoint(x: 55, y: -20), expected: .level0Index(3)), +] + +private let regularPlusFractionCases: [MTDisplayEditingTests.ClosestIndexCase] = [ + .init(point: CGPoint(x: 30, y: 0), expected: .level0Index(2)), + .init(point: CGPoint(x: 30, y: 8), expected: .level0Index(2)), + .init(point: CGPoint(x: 30, y: 40), expected: .level0Index(2)), + .init(point: CGPoint(x: 30, y: -20), expected: .level0Index(2)), + .init(point: CGPoint(x: 32, y: 0), expected: .level0Index(2)), + .init(point: CGPoint(x: 32, y: 8), expected: .level0Index(2)), + .init(point: CGPoint(x: 32, y: 40), expected: .level0Index(2)), + .init(point: CGPoint(x: 32, y: -20), expected: .level0Index(2)), + .init(point: CGPoint(x: 33, y: 0), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 33, y: 8), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 33, y: 40), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 33, y: -20), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 35, y: 0), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 35, y: 8), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 35, y: 40), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 35, y: -20), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), +] + +private let fractionPlusRegularCases: [MTDisplayEditingTests.ClosestIndexCase] = [ + .init(point: CGPoint(x: 15, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 15, y: 8), expected: .level0Index(1)), + .init(point: CGPoint(x: 15, y: 40), expected: .level0Index(1)), + .init(point: CGPoint(x: 15, y: -20), expected: .level0Index(1)), + .init(point: CGPoint(x: 13, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 13, y: 8), expected: .level0Index(1)), + .init(point: CGPoint(x: 13, y: 40), expected: .level0Index(1)), + .init(point: CGPoint(x: 13, y: -20), expected: .level0Index(1)), + .init(point: CGPoint(x: 11, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 11, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 11, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 11, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 9, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init(point: CGPoint(x: 9, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 9, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init(point: CGPoint(x: 9, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), +] + +private let exponentCases: [MTDisplayEditingTests.ClosestIndexCase] = [ + .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)), + .init(point: CGPoint(x: -10, y: -20), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: 0), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: 8), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: 40), expected: .level0Index(0)), + .init(point: CGPoint(x: 0, y: -20), expected: .level0Index(0)), + .init(point: CGPoint(x: 9, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 9, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 9, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init(point: CGPoint(x: 9, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 10, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 10, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 10, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init(point: CGPoint(x: 10, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 11, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 11, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init(point: CGPoint(x: 11, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init(point: CGPoint(x: 11, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init(point: CGPoint(x: 17, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 17, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)), + .init(point: CGPoint(x: 17, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)), + .init(point: CGPoint(x: 17, y: -20), expected: .level0Index(1)), + .init(point: CGPoint(x: 30, y: 0), expected: .level0Index(1)), + .init(point: CGPoint(x: 30, y: 8), expected: .level0Index(1)), + .init(point: CGPoint(x: 30, y: 40), expected: .level0Index(1)), + .init(point: CGPoint(x: 30, y: -20), expected: .level0Index(1)), +] From c6962b7df06f3d2be83728443970c2bcc9b47d6b Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 26 Mar 2026 18:06:12 +0000 Subject: [PATCH 120/133] Fix Swift test API and access control issues --- .../MTDisplayEditingTests.swift | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift index beb7b9c..9041662 100644 --- a/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift +++ b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift @@ -3,20 +3,20 @@ import Testing @testable import MathEditorSwift import iosMath +fileprivate struct ClosestIndexCase { + let point: CGPoint + let expected: MTMathListIndex +} + struct MTDisplayEditingTests { private let font: MTFont = MTFontManager().latinModernFont(withSize: 20) - private struct ClosestIndexCase { - let point: CGPoint - let expected: MTMathListIndex - } - private func assertClosestIndex( expression: String, cases: [ClosestIndexCase] ) { - let mathList = MTMathListBuilder.build(fromString: expression) - let displayList = MTTypesetter.createLine(forMathList: mathList, font: font, style: kMTLineStyleDisplay) + let mathList = MTMathListBuilder.build(from: expression) + let displayList = MTTypesetter.createLine(for: mathList, font: font, style: .display) for testCase in cases { let actual = displayList?.closestIndex(to: testCase.point) @@ -51,7 +51,7 @@ struct MTDisplayEditingTests { } } -private let fractionCases: [MTDisplayEditingTests.ClosestIndexCase] = [ +private let fractionCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)), .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)), .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)), @@ -86,7 +86,7 @@ private let fractionCases: [MTDisplayEditingTests.ClosestIndexCase] = [ .init(point: CGPoint(x: 20, y: -20), expected: .level0Index(1)), ] -private let regularCases: [MTDisplayEditingTests.ClosestIndexCase] = [ +private let regularCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)), .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)), .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)), @@ -121,7 +121,7 @@ private let regularCases: [MTDisplayEditingTests.ClosestIndexCase] = [ .init(point: CGPoint(x: 55, y: -20), expected: .level0Index(3)), ] -private let regularPlusFractionCases: [MTDisplayEditingTests.ClosestIndexCase] = [ +private let regularPlusFractionCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: 30, y: 0), expected: .level0Index(2)), .init(point: CGPoint(x: 30, y: 8), expected: .level0Index(2)), .init(point: CGPoint(x: 30, y: 40), expected: .level0Index(2)), @@ -140,7 +140,7 @@ private let regularPlusFractionCases: [MTDisplayEditingTests.ClosestIndexCase] = .init(point: CGPoint(x: 35, y: -20), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), ] -private let fractionPlusRegularCases: [MTDisplayEditingTests.ClosestIndexCase] = [ +private let fractionPlusRegularCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: 15, y: 0), expected: .level0Index(1)), .init(point: CGPoint(x: 15, y: 8), expected: .level0Index(1)), .init(point: CGPoint(x: 15, y: 40), expected: .level0Index(1)), @@ -159,7 +159,7 @@ private let fractionPlusRegularCases: [MTDisplayEditingTests.ClosestIndexCase] = .init(point: CGPoint(x: 9, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), ] -private let exponentCases: [MTDisplayEditingTests.ClosestIndexCase] = [ +private let exponentCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)), .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)), .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)), From 33ce0103de0f5610b594a34b0e19f80b4940f6bf Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 18:13:45 +0000 Subject: [PATCH 121/133] Fix tests --- .../Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift index 9041662..9648717 100644 --- a/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift +++ b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift @@ -8,6 +8,7 @@ fileprivate struct ClosestIndexCase { let expected: MTMathListIndex } +@Suite(.serialized) struct MTDisplayEditingTests { private let font: MTFont = MTFontManager().latinModernFont(withSize: 20) @@ -15,11 +16,11 @@ struct MTDisplayEditingTests { expression: String, cases: [ClosestIndexCase] ) { - let mathList = MTMathListBuilder.build(from: expression) + let mathList = MTMathListBuilder.build(from: expression)! let displayList = MTTypesetter.createLine(for: mathList, font: font, style: .display) for testCase in cases { - let actual = displayList?.closestIndex(to: testCase.point) + let actual = displayList.closestIndex(to: testCase.point) #expect(actual?.isEqual(testCase.expected) == true, "Index \(String(describing: actual)) does not match \(testCase.expected) for point \(testCase.point)") } From ffb4f098ebfb74dcff914b9c68bdc2dae311e638 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 18:14:15 +0000 Subject: [PATCH 122/133] fmt --- .../MTDisplayEditingTests.swift | 236 ++++++++++++++---- 1 file changed, 187 insertions(+), 49 deletions(-) diff --git a/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift index 9648717..c4b441a 100644 --- a/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift +++ b/MathEditorSwift/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift @@ -1,9 +1,10 @@ import CoreGraphics import Testing -@testable import MathEditorSwift import iosMath -fileprivate struct ClosestIndexCase { +@testable import MathEditorSwift + +private struct ClosestIndexCase { let point: CGPoint let expected: MTMathListIndex } @@ -21,8 +22,10 @@ struct MTDisplayEditingTests { for testCase in cases { let actual = displayList.closestIndex(to: testCase.point) - #expect(actual?.isEqual(testCase.expected) == true, - "Index \(String(describing: actual)) does not match \(testCase.expected) for point \(testCase.point)") + #expect( + actual?.isEqual(testCase.expected) == true, + "Index \(String(describing: actual)) does not match \(testCase.expected) for point \(testCase.point)" + ) } } @@ -61,21 +64,66 @@ private let fractionCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: -2.5, y: 0), expected: .level0Index(0)), .init(point: CGPoint(x: -2.5, y: 40), expected: .level0Index(0)), .init(point: CGPoint(x: -2.5, y: -20), expected: .level0Index(0)), - .init(point: CGPoint(x: -1, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: -1, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: -1, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: -1, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 3, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 3, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 3, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 3, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 7, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 7, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 7, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 7, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 11, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 11, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 11, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: -1, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: -1, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: -1, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: -1, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 3, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 3, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 3, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 3, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 7, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 7, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 7, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 7, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 11, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 11, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 11, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), .init(point: CGPoint(x: 11, y: -20), expected: .level0Index(1)), .init(point: CGPoint(x: 12.5, y: 8), expected: .level0Index(1)), .init(point: CGPoint(x: 12.5, y: 0), expected: .level0Index(1)), @@ -131,14 +179,38 @@ private let regularPlusFractionCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: 32, y: 8), expected: .level0Index(2)), .init(point: CGPoint(x: 32, y: 40), expected: .level0Index(2)), .init(point: CGPoint(x: 32, y: -20), expected: .level0Index(2)), - .init(point: CGPoint(x: 33, y: 0), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 33, y: 8), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 33, y: 40), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 33, y: -20), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 35, y: 0), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 35, y: 8), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 35, y: 40), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 35, y: -20), expected: MTMathListIndex(atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 33, y: 0), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 33, y: 8), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 33, y: 40), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 33, y: -20), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 35, y: 0), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 35, y: 8), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 35, y: 40), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 35, y: -20), + expected: MTMathListIndex( + atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)), ] private let fractionPlusRegularCases: [ClosestIndexCase] = [ @@ -150,14 +222,38 @@ private let fractionPlusRegularCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: 13, y: 8), expected: .level0Index(1)), .init(point: CGPoint(x: 13, y: 40), expected: .level0Index(1)), .init(point: CGPoint(x: 13, y: -20), expected: .level0Index(1)), - .init(point: CGPoint(x: 11, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 11, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 11, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 11, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 9, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), - .init(point: CGPoint(x: 9, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 9, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), - .init(point: CGPoint(x: 9, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 11, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 11, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 11, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 11, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 9, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), + .init( + point: CGPoint(x: 9, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 9, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)), + .init( + point: CGPoint(x: 9, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)), ] private let exponentCases: [ClosestIndexCase] = [ @@ -169,21 +265,63 @@ private let exponentCases: [ClosestIndexCase] = [ .init(point: CGPoint(x: 0, y: 8), expected: .level0Index(0)), .init(point: CGPoint(x: 0, y: 40), expected: .level0Index(0)), .init(point: CGPoint(x: 0, y: -20), expected: .level0Index(0)), - .init(point: CGPoint(x: 9, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), - .init(point: CGPoint(x: 9, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), - .init(point: CGPoint(x: 9, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), - .init(point: CGPoint(x: 9, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), - .init(point: CGPoint(x: 10, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), - .init(point: CGPoint(x: 10, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), - .init(point: CGPoint(x: 10, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), - .init(point: CGPoint(x: 10, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), - .init(point: CGPoint(x: 11, y: 0), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), - .init(point: CGPoint(x: 11, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), - .init(point: CGPoint(x: 11, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), - .init(point: CGPoint(x: 11, y: -20), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 9, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 9, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 9, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init( + point: CGPoint(x: 9, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 10, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 10, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 10, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init( + point: CGPoint(x: 10, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 11, y: 0), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), + .init( + point: CGPoint(x: 11, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init( + point: CGPoint(x: 11, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)), + .init( + point: CGPoint(x: 11, y: -20), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)), .init(point: CGPoint(x: 17, y: 0), expected: .level0Index(1)), - .init(point: CGPoint(x: 17, y: 8), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)), - .init(point: CGPoint(x: 17, y: 40), expected: MTMathListIndex(atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)), + .init( + point: CGPoint(x: 17, y: 8), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)), + .init( + point: CGPoint(x: 17, y: 40), + expected: MTMathListIndex( + atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)), .init(point: CGPoint(x: 17, y: -20), expected: .level0Index(1)), .init(point: CGPoint(x: 30, y: 0), expected: .level0Index(1)), .init(point: CGPoint(x: 30, y: 8), expected: .level0Index(1)), From 9edd8d6af5a5e673758088c7573f24db6e52e03a Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 18:17:40 +0000 Subject: [PATCH 123/133] Remove "Swift" from protocols --- MathEditorSwift/Package.resolved | 14 ++++++++++++++ MathEditorSwift/Package.swift | 2 +- .../MathEditorSwift/MTEditableMathLabelSwift.swift | 10 +++++----- 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 MathEditorSwift/Package.resolved diff --git a/MathEditorSwift/Package.resolved b/MathEditorSwift/Package.resolved new file mode 100644 index 0000000..24b9309 --- /dev/null +++ b/MathEditorSwift/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "iosmath", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maitbayev/iosMath.git", + "state" : { + "branch" : "master", + "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836" + } + } + ], + "version" : 2 +} diff --git a/MathEditorSwift/Package.swift b/MathEditorSwift/Package.swift index e04ad66..16e6890 100644 --- a/MathEditorSwift/Package.swift +++ b/MathEditorSwift/Package.swift @@ -22,6 +22,6 @@ let package = Package( .testTarget( name: "MathEditorSwiftTests", dependencies: ["MathEditorSwift"] - ) + ), ] ) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index 77d34c2..8dd086f 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -7,14 +7,14 @@ private let greekCapitalStart: UInt32 = 0x0391 private let greekCapitalEnd: UInt32 = 0x03A9 /// Delegate for the `MTEditableMathLabel`. All methods are optional. -public protocol MTEditableMathLabelSwiftDelegate: AnyObject { +public protocol MTEditableMathLabelDelegate: AnyObject { func returnPressed(_ label: MTEditableMathLabelSwift) func textModified(_ label: MTEditableMathLabelSwift) func didBeginEditing(_ label: MTEditableMathLabelSwift) func didEndEditing(_ label: MTEditableMathLabelSwift) } -extension MTEditableMathLabelSwiftDelegate { +extension MTEditableMathLabelDelegate { public func returnPressed(_ label: MTEditableMathLabelSwift) {} public func textModified(_ label: MTEditableMathLabelSwift) {} public func didBeginEditing(_ label: MTEditableMathLabelSwift) {} @@ -24,7 +24,7 @@ extension MTEditableMathLabelSwiftDelegate { /// This protocol provides information on the context of the current insertion point. /// The keyboard may choose to enable/disable/highlight certain parts of the UI depending on the context. /// e.g. you cannot enter the = sign when you are in a fraction so the keyboard could disable that. -public protocol MTMathKeyboardTraitsSwift: AnyObject { +public protocol MTMathKeyboardTraits { var equalsAllowed: Bool { get set } var fractionsAllowed: Bool { get set } var variablesAllowed: Bool { get set } @@ -42,7 +42,7 @@ public protocol MTMathKeyboardTraitsSwift: AnyObject { /// The keyboard should use this information to send `MTKeyInput` messages to the label. /// /// This protocol inherits from `MTMathKeyboardTraits`. -public protocol MTMathKeyboardSwift: MTMathKeyboardTraitsSwift { +public protocol MTMathKeyboardSwift: MTMathKeyboardTraits { func startedEditing(_ label: MTView & MTKeyInput) func finishedEditing(_ label: MTView & MTKeyInput) } @@ -71,7 +71,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { @objc public private(set) var cancelImage: MTCancelView? @objc private(set) var caretView: MTCaretView! - public weak var delegate: MTEditableMathLabelSwiftDelegate? + public weak var delegate: MTEditableMathLabelDelegate? public weak var keyboard: (MTView & MTMathKeyboardSwift)? @objc public var fontSize: CGFloat { From ddff3700e80f2b32e16e3acec440df2acd03cbaf Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 18:19:17 +0000 Subject: [PATCH 124/133] MTKeyboardSwift to MTKeyboard --- .../Sources/MathEditorSwift/MTEditableMathLabelSwift.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index 8dd086f..c8aae2a 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -42,7 +42,7 @@ public protocol MTMathKeyboardTraits { /// The keyboard should use this information to send `MTKeyInput` messages to the label. /// /// This protocol inherits from `MTMathKeyboardTraits`. -public protocol MTMathKeyboardSwift: MTMathKeyboardTraits { +public protocol MTMathKeyboard: MTMathKeyboardTraits { func startedEditing(_ label: MTView & MTKeyInput) func finishedEditing(_ label: MTView & MTKeyInput) } @@ -72,7 +72,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { @objc public private(set) var cancelImage: MTCancelView? @objc private(set) var caretView: MTCaretView! public weak var delegate: MTEditableMathLabelDelegate? - public weak var keyboard: (MTView & MTMathKeyboardSwift)? + public weak var keyboard: (MTView & MTMathKeyboard)? @objc public var fontSize: CGFloat { get { label.fontSize } From a469f5612147aa68646197e3664caf345d7dfadf Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 18:19:48 +0000 Subject: [PATCH 125/133] Fix build --- .../MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index 5261454..0e0c222 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -4,7 +4,7 @@ import SwiftUI import UIKit - public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboardSwift, + public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboard, UIInputViewAudioFeedback { private static let defaultTab: KeyboardTab = .numbers From f09656e634e03f0b0454267a9a8119ab13065656 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Thu, 26 Mar 2026 18:34:18 +0000 Subject: [PATCH 126/133] Simplify keyboard state updates in MTEditableMathLabelSwift --- .../MTEditableMathLabelSwift.swift | 39 +++++-------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index c8aae2a..823c966 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -448,21 +448,21 @@ extension MTEditableMathLabelSwift { } fileprivate func setKeyboardMode() { - setKeyboardValue(false, forKey: "exponentHighlighted") - setKeyboardValue(false, forKey: "radicalHighlighted") - setKeyboardValue(false, forKey: "squareRootHighlighted") + keyboard?.exponentHighlighted = false + keyboard?.radicalHighlighted = false + keyboard?.squareRootHighlighted = false if insertionIndex?.hasSubIndex(of: .subIndexTypeSuperscript) == true { - setKeyboardValue(true, forKey: "exponentHighlighted") - setKeyboardValue(false, forKey: "equalsAllowed") + keyboard?.exponentHighlighted = true + keyboard?.equalsAllowed = false } if insertionIndex?.subIndexType == .subIndexTypeNumerator { - setKeyboardValue(false, forKey: "equalsAllowed") + keyboard?.equalsAllowed = false } if insertionIndex?.subIndexType == .subIndexTypeDegree { - setKeyboardValue(true, forKey: "radicalHighlighted") + keyboard?.radicalHighlighted = true } else if insertionIndex?.subIndexType == .subIndexTypeRadicand { - setKeyboardValue(true, forKey: "squareRootHighlighted") + keyboard?.squareRootHighlighted = true } } @@ -764,26 +764,5 @@ extension MTEditableMathLabelSwift { return list } - fileprivate func setKeyboardValue(_ value: Bool, forKey key: String) { - switch key { - case "equalsAllowed": - keyboard?.equalsAllowed = value - case "fractionsAllowed": - keyboard?.fractionsAllowed = value - case "variablesAllowed": - keyboard?.variablesAllowed = value - case "numbersAllowed": - keyboard?.numbersAllowed = value - case "operatorsAllowed": - keyboard?.operatorsAllowed = value - case "exponentHighlighted": - keyboard?.exponentHighlighted = value - case "squareRootHighlighted": - keyboard?.squareRootHighlighted = value - case "radicalHighlighted": - keyboard?.radicalHighlighted = value - default: - break - } - } + } From b0aee31950c542d419599facd6e9580f258d72c1 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 19:04:45 +0000 Subject: [PATCH 127/133] revert Package.swift --- Package.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index cec4182..2028ca1 100644 --- a/Package.swift +++ b/Package.swift @@ -16,12 +16,11 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master"), - .package(path: "./MathEditorSwift"), ], targets: [ .target( name: "MathEditor", - dependencies: ["iosMath", "MathEditorSwift"], + dependencies: ["iosMath"], path: "./mathEditor", cSettings: [ .headerSearchPath("./editor"), From 02cc1d990cc70cc8e53f8afe1a82d5bc4ea6514f Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 19:06:41 +0000 Subject: [PATCH 128/133] Revert --- Package.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 2028ca1..9f1716e 100644 --- a/Package.swift +++ b/Package.swift @@ -15,12 +15,12 @@ let package = Package( targets: ["MathKeyboard"]), ], dependencies: [ - .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master"), + .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master") ], targets: [ .target( name: "MathEditor", - dependencies: ["iosMath"], + dependencies: [.product(name: "iosMath", package: "iosMath")], path: "./mathEditor", cSettings: [ .headerSearchPath("./editor"), @@ -29,7 +29,7 @@ let package = Package( ), .target( name: "MathKeyboard", - dependencies: ["iosMath", "MathEditor"], + dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditor"], path: "./mathKeyboard", resources: [.process("MathKeyboardResources")], cSettings: [ From 34b45b7451cf021a880d1ba881550654c524a07f Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 19:11:05 +0000 Subject: [PATCH 129/133] Fix disabling equal --- .../Sources/MathEditorSwift/MTEditableMathLabelSwift.swift | 5 ++++- MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index 823c966..c2e402b 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -451,6 +451,7 @@ extension MTEditableMathLabelSwift { keyboard?.exponentHighlighted = false keyboard?.radicalHighlighted = false keyboard?.squareRootHighlighted = false + keyboard?.equalsAllowed = true if insertionIndex?.hasSubIndex(of: .subIndexTypeSuperscript) == true { keyboard?.exponentHighlighted = true @@ -458,7 +459,10 @@ extension MTEditableMathLabelSwift { } if insertionIndex?.subIndexType == .subIndexTypeNumerator { keyboard?.equalsAllowed = false + } else if insertionIndex?.subIndexType == .subIndexTypeDenominator { + keyboard?.equalsAllowed = false } + if insertionIndex?.subIndexType == .subIndexTypeDegree { keyboard?.radicalHighlighted = true } else if insertionIndex?.subIndexType == .subIndexTypeRadicand { @@ -764,5 +768,4 @@ extension MTEditableMathLabelSwift { return list } - } diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift b/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift index a1ad8a1..dca95ad 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTKeyInput.swift @@ -5,7 +5,7 @@ // Created by Madiyar Aitbayev on 26/03/2026. // -#if canImport(UIKit) +#if os(iOS) import UIKit public typealias MTKeyInput = UIKeyInput #else From 5529ecaa81fbc7215b877530fc0a83a7bc24ad20 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 19:42:54 +0000 Subject: [PATCH 130/133] Fix MathEditor.xcodeproj --- MathEditor.xcodeproj/project.pbxproj | 14 +++++++++++--- .../xcschemes/iosMathEditor-Example.xcscheme | 2 +- MathEditor.xcworkspace/contents.xcworkspacedata | 10 ---------- MathEditorExample/MTViewController.m | 3 ++- 4 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 MathEditor.xcworkspace/contents.xcworkspacedata diff --git a/MathEditor.xcodeproj/project.pbxproj b/MathEditor.xcodeproj/project.pbxproj index 593056d..f4e4328 100644 --- a/MathEditor.xcodeproj/project.pbxproj +++ b/MathEditor.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; 862D28DD2E2D4C4400F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28DC2E2D4C4400F9B6FE /* MathEditor */; }; + 86B937A02F75C33F00FFC54B /* MathKeyboard in Frameworks */ = {isa = PBXBuildFile; productRef = 86B9379F2F75C33F00FFC54B /* MathKeyboard */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; /* End PBXBuildFile section */ @@ -105,6 +106,7 @@ 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */, 6003F592195388D20070C39A /* UIKit.framework in Frameworks */, 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */, + 86B937A02F75C33F00FFC54B /* MathKeyboard in Frameworks */, 862D28DD2E2D4C4400F9B6FE /* MathEditor in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -332,7 +334,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; CLASSPREFIX = MT; - LastUpgradeCheck = 1640; + LastUpgradeCheck = 2630; ORGANIZATIONNAME = "Kostub Deshmukh"; TargetAttributes = { 490BE5551CE695CC00AE31A0 = { @@ -342,10 +344,9 @@ }; buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "MathEditor" */; compatibilityVersion = "Xcode 6.3"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); @@ -528,6 +529,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -576,6 +578,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -702,6 +705,11 @@ isa = XCSwiftPackageProductDependency; productName = MathEditor; }; + 86B9379F2F75C33F00FFC54B /* MathKeyboard */ = { + isa = XCSwiftPackageProductDependency; + package = 862D28DB2E2D4C4400F9B6FE /* XCLocalSwiftPackageReference "../MathEditor" */; + productName = MathKeyboard; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 6003F582195388D10070C39A /* Project object */; diff --git a/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme b/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme index ba87031..f7dfae5 100644 --- a/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme +++ b/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme @@ -1,6 +1,6 @@ - - - - - - diff --git a/MathEditorExample/MTViewController.m b/MathEditorExample/MTViewController.m index 0ed30c7..e782a79 100644 --- a/MathEditorExample/MTViewController.m +++ b/MathEditorExample/MTViewController.m @@ -10,7 +10,8 @@ // #import "MTViewController.h" -#import "MTMathKeyboardRootView.h" + +@import MathKeyboard; @interface MTViewController () From 7db5519809eff873a93a3f079a01f627378f95d4 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 20:05:28 +0000 Subject: [PATCH 131/133] Remove more platform guards from keyboard --- .../Internal/MTView/MTView+AutoLayout.swift | 4 +- MathEditorSwift/todo.md | 2 +- .../KeyboardContainerView.swift | 52 +++++------ ...ompat.swift => KeyboardFontRegistry.swift} | 0 .../MTView+AutoLayout.swift | 37 -------- .../MathKeyboardRootView.swift | 90 +++++++++---------- 6 files changed, 68 insertions(+), 117 deletions(-) rename MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/{MTMathKeyboardCompat.swift => KeyboardFontRegistry.swift} (100%) delete mode 100644 MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift index 03ab73e..c38bef3 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift @@ -14,11 +14,11 @@ import Foundation #endif extension MTView { - @objc public func pinToSuperview() { + public func pinToSuperview() { pinToSuperview(withTop: 0, leading: 0, bottom: 0, trailing: 0) } - @objc public func pinToSuperview( + public func pinToSuperview( withTop top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat ) { guard let superview else { return } diff --git a/MathEditorSwift/todo.md b/MathEditorSwift/todo.md index 8e87e28..756585c 100644 --- a/MathEditorSwift/todo.md +++ b/MathEditorSwift/todo.md @@ -15,7 +15,7 @@ 1. - [x] MTKeyInputSwift should be alias to UIKeyInput 2. - [x] Bring comments back 3. - [ ] highlightColor is different in `initialize` -4. - [ ] layoutLabelIfNeeded should be the same as objc +4. - [x] layoutLabelIfNeeded should be the same as objc 5. - [x] setNeedsDisplayCompat should be changed 6. - [x] setLabelNeedsDisplayCompat should be changed 7. - [ ] getIndexAfterSpecialStructure: is next should unwrapped? diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift index 77c5154..e8df39a 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/KeyboardContainerView.swift @@ -5,36 +5,32 @@ // Created by Madiyar Aitbayev on 22/03/2026. // -#if os(iOS) +import SwiftUI - import SwiftUI +struct KeyboardContainerView: View { + let state: KeyboardState + let onAction: (KeyboardAction) -> Void - struct KeyboardContainerView: View { - let state: KeyboardState - let onAction: (KeyboardAction) -> Void - - var body: some View { - keyboardView(for: state.currentTab) - } + var body: some View { + keyboardView(for: state.currentTab) + } - @ViewBuilder - private func keyboardView(for tab: KeyboardTab) -> some View { - switch tab { - case .numbers: - NumbersKeyboardView(state: state, onAction: onAction) - case .operations: - OperationsKeyboardView(state: state, onAction: onAction) - case .functions: - FunctionsKeyboardView(state: state, onAction: onAction) - case .letters: - LettersKeyboardView( - state: state, - isLowercase: state.isLowercase, - onShift: { onAction(.toggleShift) }, - onAction: onAction - ) - } + @ViewBuilder + private func keyboardView(for tab: KeyboardTab) -> some View { + switch tab { + case .numbers: + NumbersKeyboardView(state: state, onAction: onAction) + case .operations: + OperationsKeyboardView(state: state, onAction: onAction) + case .functions: + FunctionsKeyboardView(state: state, onAction: onAction) + case .letters: + LettersKeyboardView( + state: state, + isLowercase: state.isLowercase, + onShift: { onAction(.toggleShift) }, + onAction: onAction + ) } } - -#endif // os(iOS) +} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardFontRegistry.swift similarity index 100% rename from MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/MTMathKeyboardCompat.swift rename to MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/Keyboards/KeyboardFontRegistry.swift diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift deleted file mode 100644 index 9f9124a..0000000 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTView+AutoLayout.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation - -#if os(iOS) - import UIKit - typealias MTView = UIView - typealias MTViewEdgeInsets = UIEdgeInsets -#elseif os(macOS) - import AppKit - typealias MTView = NSView - typealias MTViewEdgeInsets = NSEdgeInsets -#endif - -extension MTView { - func pinToSuperview() { - pinToSuperview(insets: .zero) - } - - func pinToSuperview(insets: MTViewEdgeInsets) { - guard let superview else { return } - - translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - topAnchor.constraint(equalTo: superview.topAnchor, constant: insets.top), - leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: insets.left), - trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -insets.right), - bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -insets.bottom), - ]) - } -} - -extension MTViewEdgeInsets { - #if os(macOS) - static var zero: Self { - NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - } - #endif -} diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift index 445b93b..f8f717f 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MathKeyboardRootView.swift @@ -7,59 +7,51 @@ import SwiftUI -#if os(iOS) - - public struct MathKeyboardRootView: View { - let state: KeyboardState - let onTabSelected: (KeyboardTab) -> Void - let onAction: (KeyboardAction) -> Void - - public var body: some View { - GeometryReader { proxy in - let totalHeight = proxy.size.height - let tabHeight = totalHeight / 5.0 - let keyboardHeight = totalHeight - tabHeight - - VStack(spacing: 0) { - HStack(spacing: 0) { - ForEach(KeyboardTab.allCases) { tab in - Button { - onTabSelected(tab) - } label: { - Image(uiImage: tabImage(for: tab)) - .renderingMode(.original) - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(.horizontal, 8) - .padding(.vertical, 6) - } - .buttonStyle(.plain) - .background(Color(white: 0.768627451)) +public struct MathKeyboardRootView: View { + let state: KeyboardState + let onTabSelected: (KeyboardTab) -> Void + let onAction: (KeyboardAction) -> Void + + public var body: some View { + GeometryReader { proxy in + let totalHeight = proxy.size.height + let tabHeight = totalHeight / 5.0 + let keyboardHeight = totalHeight - tabHeight + + VStack(spacing: 0) { + HStack(spacing: 0) { + ForEach(KeyboardTab.allCases) { tab in + Button { + onTabSelected(tab) + } label: { + Image(tab.imageName(for: state), bundle: .module) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal, 8) + .padding(.vertical, 6) } + .buttonStyle(.plain) + .background(Color(white: 0.768627451)) } - .frame(height: tabHeight) - - KeyboardContainerView( - state: state, - onAction: onAction - ) - .frame(height: keyboardHeight) } - .background(Color.white) - .ignoresSafeArea() - } - } + .frame(height: tabHeight) - private func tabImage(for tab: KeyboardTab) -> UIImage { - let names = tab.imageNames - let name = state.currentTab == tab ? names.selected : names.normal - return UIImage( - named: name, - in: .module, - compatibleWith: nil - ) ?? UIImage() + KeyboardContainerView( + state: state, + onAction: onAction + ) + .frame(height: keyboardHeight) + } + .background(Color.white) + .ignoresSafeArea() } } +} -#endif // os(iOS) +extension KeyboardTab { + fileprivate func imageName(for state: KeyboardState) -> String { + state.currentTab == self ? imageNames.selected : imageNames.normal + } +} From 660114133d5128e0bb082c07893f5a6ddb39a2b8 Mon Sep 17 00:00:00 2001 From: Madiyar Aitbayev Date: Thu, 26 Mar 2026 20:39:12 +0000 Subject: [PATCH 132/133] Text color is correct --- .../MathEditorSwift/MTEditableMathLabelSwift.swift | 4 ++-- .../MathEditorSwiftUIExample/ContentView.swift | 3 +++ .../MTMathKeyboardSwiftUIRootView.swift | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift index c2e402b..f7a1608 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift @@ -42,7 +42,7 @@ public protocol MTMathKeyboardTraits { /// The keyboard should use this information to send `MTKeyInput` messages to the label. /// /// This protocol inherits from `MTMathKeyboardTraits`. -public protocol MTMathKeyboard: MTMathKeyboardTraits { +public protocol MTMathKeyboard: AnyObject, MTMathKeyboardTraits { func startedEditing(_ label: MTView & MTKeyInput) func finishedEditing(_ label: MTView & MTKeyInput) } @@ -72,7 +72,7 @@ public final class MTEditableMathLabelSwift: MTView, MTKeyInput { @objc public private(set) var cancelImage: MTCancelView? @objc private(set) var caretView: MTCaretView! public weak var delegate: MTEditableMathLabelDelegate? - public weak var keyboard: (MTView & MTMathKeyboard)? + public weak var keyboard: MTMathKeyboard? @objc public var fontSize: CGFloat { get { label.fontSize } diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift index 0c61807..924c5d3 100644 --- a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift +++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift @@ -25,6 +25,9 @@ struct ContentView: View { let mathLabel = MTEditableMathLabelSwift() mathLabel.backgroundColor = .clear mathLabel.keyboard = MTMathKeyboardSwiftUIRootView.sharedInstance() + mathLabel.textColor = .label + mathLabel.caretColor = .label + mathLabel.startEditing() return mathLabel } diff --git a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift index 0e0c222..c662f75 100644 --- a/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift +++ b/MathKeyboardSwiftUI/Sources/MathKeyboardSwiftUI/MTMathKeyboardSwiftUIRootView.swift @@ -4,14 +4,14 @@ import SwiftUI import UIKit - public final class MTMathKeyboardSwiftUIRootView: UIView, MTMathKeyboard, + public final class MTMathKeyboardSwiftUIRootView: MTView, MTMathKeyboard, UIInputViewAudioFeedback { private static let defaultTab: KeyboardTab = .numbers private static let shared = MTMathKeyboardSwiftUIRootView() private var state = KeyboardState() - private weak var textInput: (any UIView & UIKeyInput)? + private weak var textInput: (any MTView & MTKeyInput)? private lazy var hostingController = UIHostingController( rootView: makeRootView() ) @@ -78,12 +78,12 @@ set { updateState { $0.radicalHighlighted = newValue } } } - public func startedEditing(_ label: any UIView & UIKeyInput) { + public func startedEditing(_ label: any MTView & MTKeyInput) { textInput = label updateRootView() } - public func finishedEditing(_ label: any UIView & UIKeyInput) { + public func finishedEditing(_ label: any MTView & MTKeyInput) { if textInput === label { textInput = nil updateRootView() From 4688fcda978c8eea31d48543edca896b71e8cd50 Mon Sep 17 00:00:00 2001 From: Madiyar Date: Fri, 27 Mar 2026 22:50:02 +0000 Subject: [PATCH 133/133] Fix warnings --- .../Internal/MTDisplay+Editing.swift | 4 ++-- .../Internal/MTMathList+Editing.swift | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift index 5477758..a1f8779 100644 --- a/MathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift +++ b/MathEditorSwift/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift @@ -145,7 +145,7 @@ extension MTCTLineDisplay { if strLenCovered >= strIndex { return UInt(mlIndex) } - guard let atom = atoms[mlIndex] as? MTMathAtom else { continue } + let atom = atoms[mlIndex] strLenCovered += UInt(atom.nucleus.count) } // By the end we should have covered all characters that can be addressed. @@ -159,7 +159,7 @@ extension MTCTLineDisplay { var strIndex = 0 for i in 0.. 0 { - previous = atoms[Int(index.atomIndex - 1)] as? MTMathAtom + previous = atoms[Int(index.atomIndex - 1)] } if let previous, previous.subScript == nil, @@ -160,14 +160,14 @@ extension MTMathList { case .subIndexTypeSubscript: let atomIndex = Int(index.atomIndex) - guard let current = atoms[atomIndex] as? MTMathAtom else { return } + let current = atoms[atomIndex] assert(current.subScript != nil, "No subscript for atom at index \(index.atomIndex)") guard let subIndex = index.sub else { return } current.subScript?.removeAtom(atListIndex: subIndex) case .subIndexTypeSuperscript: let atomIndex = Int(index.atomIndex) - guard let current = atoms[atomIndex] as? MTMathAtom else { return } + let current = atoms[atomIndex] assert(current.superScript != nil, "No superscript for atom at index \(index.atomIndex)") guard let subIndex = index.sub else { return } current.superScript?.removeAtom(atListIndex: subIndex) @@ -221,14 +221,14 @@ extension MTMathList { case .subIndexTypeSubscript: let atomIndex = Int(start.atomIndex) - guard let current = atoms[atomIndex] as? MTMathAtom else { return } + let current = atoms[atomIndex] assert(current.subScript != nil, "No subscript for atom at index \(start.atomIndex)") guard let subIndexRange = range.subIndex() else { return } current.subScript?.removeAtoms(inListIndexRange: subIndexRange) case .subIndexTypeSuperscript: let atomIndex = Int(start.atomIndex) - guard let current = atoms[atomIndex] as? MTMathAtom else { return } + let current = atoms[atomIndex] assert(current.superScript != nil, "No superscript for atom at index \(start.atomIndex)") guard let subIndexRange = range.subIndex() else { return } current.superScript?.removeAtoms(inListIndexRange: subIndexRange) @@ -245,7 +245,7 @@ extension MTMathList { public func atom(atListIndex index: MTMathListIndex?) -> MTMathAtom? { guard let index else { return nil } guard index.atomIndex < UInt(atoms.count) else { return nil } - guard let atom = atoms[Int(index.atomIndex)] as? MTMathAtom else { return nil } + let atom = atoms[Int(index.atomIndex)] switch index.subIndexType { case .subIndexTypeNone, .subIndexTypeNucleus: