FreeRDP
Loading...
Searching...
No Matches
RDPKeyboardToolbarView.m
1/*
2 Toolbar of modifier and special keys shown above the ios keyboard
3
4 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
5 If a copy of the MPL was not distributed with this file, You can obtain one at
6 http://mozilla.org/MPL/2.0/.
7 */
8
9#import "RDPKeyboardToolbarView.h"
10#import "RDPOverlayStyle.h"
11#import "RDPKeyboard.h"
12#import "Utils.h"
13
14// index of the keys in _key_buttons
15enum
16{
17 ACCESSORY_KEY_WIN,
18 ACCESSORY_KEY_ESC,
19 ACCESSORY_KEY_SHIFT,
20 ACCESSORY_KEY_CTRL,
21 ACCESSORY_KEY_ALT,
22 ACCESSORY_KEY_ALTGR,
23 ACCESSORY_KEY_DEL,
24 ACCESSORY_KEY_TAB,
25 ACCESSORY_KEY_COUNT
26};
27
28static const CGFloat kViewHeight = 64.0;
29static const CGFloat kBarHeight = 52.0;
30static const CGFloat kBarSideMargin = 16.0; // between the screen (safe area) and the bar
31static const CGFloat kKeyInset = 16.0; // between the bar's ends and the first/last key
32static const CGFloat kKeyHeight = 36.0;
33static const CGFloat kKeySpacing = 10.0;
34static const CGFloat kDividerGap = 15.0; // on either side of the divider
35static const CGFloat kDividerHeight = 25.0;
36static const CGFloat kFadeWidth = 24.0;
37
38// metrics that differ between iPhone and iPad
39typedef struct
40{
41 CGFloat keyWidth;
42 CGFloat logoSize; // Windows logo
43 CGFloat symbolSize; // advanced keyboard toggle icon
44} RDPKeyboardToolbarMetrics;
45
46static const RDPKeyboardToolbarMetrics kPhoneMetrics = { 56.0, 18.0, 17.0 };
47static const RDPKeyboardToolbarMetrics kPadMetrics = { 64.0, 20.0, 19.0 };
48
49static const RDPKeyboardToolbarMetrics *RDPGetKeyboardToolbarMetrics(void)
50{
51 return IsPad() ? &kPadMetrics : &kPhoneMetrics;
52}
53
54@interface RDPKeyScrollView : UIScrollView
55@end
56
57@implementation RDPKeyScrollView
58
59- (BOOL)touchesShouldCancelInContentView:(UIView *)view
60{
61 (void)view;
62 return YES;
63}
64
65@end
66
67@interface RDPKeyboardToolbarView (Private)
68- (void)addKeyWithTitle:(NSString *)title
69 image:(UIImage *)image
70 accessibilityLabel:(NSString *)label
71 target:(id)target
72 action:(SEL)action;
73- (void)updateFadeMask;
74@end
75
76@implementation RDPKeyboardToolbarView
77
78- (id)initWithTarget:(id)target
79{
80 self = [super initWithFrame:CGRectMake(0, 0, 320, kViewHeight)];
81 if (self)
82 {
83 [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
84
85 _bar = [[UIView alloc] initWithFrame:CGRectZero];
86 [self addSubview:_bar];
87
88 _background = [RDPCreateGlassCapsuleView() retain];
89 [_bar addSubview:_background];
90
91 // the scroll view sits in a container so the fade mask stays put while scrolling
92 _keys_container = [[UIView alloc] initWithFrame:CGRectZero];
93 [_bar addSubview:_keys_container];
94
95 _keys_scrollview = [[RDPKeyScrollView alloc] initWithFrame:CGRectZero];
96 [_keys_scrollview setShowsHorizontalScrollIndicator:NO];
97 [_keys_scrollview setDelaysContentTouches:NO];
98 [_keys_scrollview setDelegate:self];
99 [_keys_container addSubview:_keys_scrollview];
100
101 _fade_mask = [[CAGradientLayer layer] retain];
102 [_fade_mask setStartPoint:CGPointMake(0.0, 0.5)];
103 [_fade_mask setEndPoint:CGPointMake(1.0, 0.5)];
104
105 const RDPKeyboardToolbarMetrics *metrics = RDPGetKeyboardToolbarMetrics();
106 _key_buttons = [[NSMutableArray alloc] initWithCapacity:ACCESSORY_KEY_COUNT];
107
108 // buttons
109 [self addKeyWithTitle:nil
110 image:RDPWindowsLogoImage(metrics->logoSize)
111 accessibilityLabel:NSLocalizedString(@"Windows", @"Windows key")
112 target:target
113 action:@selector(toggleWinKey:)];
114 [self addKeyWithTitle:@"esc"
115 image:nil
116 accessibilityLabel:NSLocalizedString(@"Escape", @"Escape key")
117 target:target
118 action:@selector(pressEscKey:)];
119 [self addKeyWithTitle:@"shift"
120 image:nil
121 accessibilityLabel:NSLocalizedString(@"Shift", @"Shift key")
122 target:target
123 action:@selector(toggleShiftKey:)];
124 [self addKeyWithTitle:@"ctrl"
125 image:nil
126 accessibilityLabel:NSLocalizedString(@"Control", @"Control key")
127 target:target
128 action:@selector(toggleCtrlKey:)];
129 [self addKeyWithTitle:@"alt"
130 image:nil
131 accessibilityLabel:NSLocalizedString(@"Alt", @"Alt key")
132 target:target
133 action:@selector(toggleAltKey:)];
134 [self addKeyWithTitle:@"alt gr"
135 image:nil
136 accessibilityLabel:NSLocalizedString(@"Alt Gr", @"Alt Gr key")
137 target:target
138 action:@selector(toggleAltGrKey:)];
139 [self addKeyWithTitle:@"del"
140 image:nil
141 accessibilityLabel:NSLocalizedString(@"Delete", @"Delete key")
142 target:target
143 action:@selector(pressDeleteKey:)];
144 [self addKeyWithTitle:@"tab"
145 image:nil
146 accessibilityLabel:NSLocalizedString(@"Tab", @"Tab key")
147 target:target
148 action:@selector(pressTabKey:)];
149
150 _divider = [[UIView alloc] initWithFrame:CGRectZero];
151
152 [_divider setBackgroundColor:[UIColor colorWithWhite:0.5 alpha:0.6]];
153 [_bar addSubview:_divider];
154
155 UIImageSymbolConfiguration *config =
156 [UIImageSymbolConfiguration configurationWithPointSize:metrics->symbolSize
157 weight:UIImageSymbolWeightMedium];
158 _toggle_button =
159 [RDPCreateKeyButton(nil, [UIImage systemImageNamed:@"arrow.right.arrow.left"
160 withConfiguration:config]) retain];
161 [_toggle_button setAccessibilityLabel:NSLocalizedString(@"Extended keyboard",
162 @"Extended keyboard toggle")];
163 [_toggle_button addTarget:target
164 action:@selector(toggleKeyboardWhenOtherVisible:)
165 forControlEvents:UIControlEventTouchUpInside];
166 [_toggle_button addTarget:self
167 action:@selector(playInputClick:)
168 forControlEvents:UIControlEventTouchUpInside];
169 [_bar addSubview:_toggle_button];
170 }
171 return self;
172}
173
174- (void)dealloc
175{
176 [_bar release];
177 [_background release];
178 [_keys_container release];
179 [_keys_scrollview setDelegate:nil];
180 [_keys_scrollview release];
181 [_fade_mask release];
182 [_key_buttons release];
183 [_divider release];
184 [_toggle_button release];
185 [super dealloc];
186}
187
188- (CGSize)intrinsicContentSize
189{
190 return CGSizeMake(UIViewNoIntrinsicMetric, kViewHeight);
191}
192
193- (void)safeAreaInsetsDidChange
194{
195 [super safeAreaInsetsDidChange];
196 [self setNeedsLayout];
197}
198
199- (void)layoutSubviews
200{
201 [super layoutSubviews];
202
203 UIEdgeInsets safe = [self safeAreaInsets];
204 CGFloat barWidth = [self bounds].size.width - safe.left - safe.right - 2 * kBarSideMargin;
205 [_bar setFrame:CGRectMake(safe.left + kBarSideMargin, (kViewHeight - kBarHeight) * 0.5,
206 barWidth, kBarHeight)];
207 [_background setFrame:[_bar bounds]];
208 RDPUpdateGlassCapsuleCorners(_background);
209
210 const CGFloat keyWidth = RDPGetKeyboardToolbarMetrics()->keyWidth;
211 const CGFloat keyY = (kBarHeight - kKeyHeight) * 0.5;
212 const CGFloat toggleX = barWidth - kKeyInset - keyWidth;
213 const CGFloat dividerX = toggleX - kDividerGap;
214
215 [_toggle_button setFrame:CGRectMake(toggleX, keyY, keyWidth, kKeyHeight)];
216 [_divider setFrame:CGRectMake(dividerX - 0.5, (kBarHeight - kDividerHeight) * 0.5, 1.0,
217 kDividerHeight)];
218
219 // remaining keys share the space in front of the divider
220 const CGFloat keysWidth = MAX(dividerX - kDividerGap - kKeyInset, 0.0);
221 [_keys_container setFrame:CGRectMake(kKeyInset, 0.0, keysWidth, kBarHeight)];
222 [_keys_scrollview setFrame:[_keys_container bounds]];
223
224 const NSUInteger count = [_key_buttons count];
225 const CGFloat spacingTotal = kKeySpacing * (count - 1);
226 CGFloat width = keyWidth;
227 if (keysWidth > count * keyWidth + spacingTotal)
228 width = MIN((keysWidth - spacingTotal) / count, keyWidth * 1.35);
229
230 CGFloat x = 0.0;
231 for (UIButton *btn in _key_buttons)
232 {
233 [btn setFrame:CGRectMake(x, keyY, width, kKeyHeight)];
234 x += width + kKeySpacing;
235 }
236 [_keys_scrollview setContentSize:CGSizeMake(MAX(x - kKeySpacing, 0.0), kBarHeight)];
237
238 [self updateFadeMask];
239}
240
241- (void)updateModifiersWithKeyboard:(RDPKeyboard *)keyboard
242{
243 [[_key_buttons objectAtIndex:ACCESSORY_KEY_WIN] setSelected:[keyboard winPressed]];
244 [[_key_buttons objectAtIndex:ACCESSORY_KEY_SHIFT] setSelected:[keyboard shiftPressed]];
245 [[_key_buttons objectAtIndex:ACCESSORY_KEY_CTRL] setSelected:[keyboard ctrlPressed]];
246 [[_key_buttons objectAtIndex:ACCESSORY_KEY_ALT] setSelected:[keyboard altPressed]];
247 [[_key_buttons objectAtIndex:ACCESSORY_KEY_ALTGR] setSelected:[keyboard altGrPressed]];
248}
249
250- (void)setExtendedKeyboardActive:(BOOL)active
251{
252 [_toggle_button setSelected:active];
253}
254
255- (BOOL)enableInputClicksWhenVisible
256{
257 return YES;
258}
259
260- (void)playInputClick:(id)sender
261{
262 (void)sender;
263 [[UIDevice currentDevice] playInputClick];
264}
265
266#pragma mark - UIScrollViewDelegate
267
268- (void)scrollViewDidScroll:(UIScrollView *)scrollView
269{
270 (void)scrollView;
271 [self updateFadeMask];
272}
273
274@end
275
276@implementation RDPKeyboardToolbarView (Private)
277
278- (void)addKeyWithTitle:(NSString *)title
279 image:(UIImage *)image
280 accessibilityLabel:(NSString *)label
281 target:(id)target
282 action:(SEL)action
283{
284 UIButton *btn = RDPCreateKeyButton(title, image);
285 [btn setAccessibilityLabel:label];
286 [btn addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
287 [btn addTarget:self
288 action:@selector(playInputClick:)
289 forControlEvents:UIControlEventTouchUpInside];
290 [_keys_scrollview addSubview:btn];
291 [_key_buttons addObject:btn];
292}
293
294// if the button overflows, display a fade out.
295- (void)updateFadeMask
296{
297 CGSize size = [_keys_scrollview bounds].size;
298 CGFloat offset = [_keys_scrollview contentOffset].x;
299 CGFloat overflow = [_keys_scrollview contentSize].width - size.width;
300
301 // if the button does not overflow or does not overflow sufficiently
302 // off the fade out
303 if (size.width <= 0.0 || overflow <= 0.5)
304 {
305 [[_keys_container layer] setMask:nil];
306 return;
307 }
308
309 const CGFloat fade = MIN(kFadeWidth / size.width, 0.5);
310 BOOL fadeLeading = offset > 0.5;
311 BOOL fadeTrailing = offset < overflow - 0.5;
312 id opaque = (id)[[UIColor blackColor] CGColor];
313 id clear = (id)[[UIColor clearColor] CGColor];
314
315 // turn off animation
316 [CATransaction begin];
317 [CATransaction setDisableActions:YES];
318 [_fade_mask setFrame:[_keys_container bounds]];
319 [_fade_mask
320 setColors:@[fadeLeading ? clear : opaque, opaque, opaque, fadeTrailing ? clear : opaque]];
321 [_fade_mask setLocations:@[@0.0, @(fade), @(1.0 - fade), @1.0]];
322 [[_keys_container layer] setMask:_fade_mask];
323 [CATransaction commit];
324}
325
326@end