SoftwareKeyboardRendererBase.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. using Ryujinx.HLE.Ui;
  2. using Ryujinx.Memory;
  3. using SixLabors.ImageSharp;
  4. using SixLabors.ImageSharp.Processing;
  5. using SixLabors.ImageSharp.Drawing.Processing;
  6. using SixLabors.Fonts;
  7. using System;
  8. using System.Diagnostics;
  9. using System.IO;
  10. using System.Numerics;
  11. using System.Reflection;
  12. using System.Runtime.InteropServices;
  13. using SixLabors.ImageSharp.PixelFormats;
  14. namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
  15. {
  16. /// <summary>
  17. /// Base class that generates the graphics for the software keyboard applet during inline mode.
  18. /// </summary>
  19. internal class SoftwareKeyboardRendererBase
  20. {
  21. public const int TextBoxBlinkThreshold = 8;
  22. const string MessageText = "Please use the keyboard to input text";
  23. const string AcceptText = "Accept";
  24. const string CancelText = "Cancel";
  25. const string ControllerToggleText = "Toggle input";
  26. private readonly object _bufferLock = new object();
  27. private RenderingSurfaceInfo _surfaceInfo = null;
  28. private Image<Argb32> _surface = null;
  29. private byte[] _bufferData = null;
  30. private Image _ryujinxLogo = null;
  31. private Image _padAcceptIcon = null;
  32. private Image _padCancelIcon = null;
  33. private Image _keyModeIcon = null;
  34. private float _textBoxOutlineWidth;
  35. private float _padPressedPenWidth;
  36. private Color _textNormalColor;
  37. private Color _textSelectedColor;
  38. private Color _textOverCursorColor;
  39. private IBrush _panelBrush;
  40. private IBrush _disabledBrush;
  41. private IBrush _cursorBrush;
  42. private IBrush _selectionBoxBrush;
  43. private Pen _textBoxOutlinePen;
  44. private Pen _cursorPen;
  45. private Pen _selectionBoxPen;
  46. private Pen _padPressedPen;
  47. private int _inputTextFontSize;
  48. private Font _messageFont;
  49. private Font _inputTextFont;
  50. private Font _labelsTextFont;
  51. private RectangleF _panelRectangle;
  52. private Point _logoPosition;
  53. private float _messagePositionY;
  54. public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
  55. {
  56. int ryujinxLogoSize = 32;
  57. string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png";
  58. _ryujinxLogo = LoadResource(Assembly.GetExecutingAssembly(), ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize);
  59. string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
  60. string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
  61. string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
  62. _padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
  63. _padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
  64. _keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
  65. Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
  66. Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
  67. Color borderColor = ToColor(uiTheme.DefaultBorderColor);
  68. Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
  69. _textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
  70. _textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
  71. _textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
  72. float cursorWidth = 2;
  73. _textBoxOutlineWidth = 2;
  74. _padPressedPenWidth = 2;
  75. _panelBrush = new SolidBrush(panelColor);
  76. _disabledBrush = new SolidBrush(panelTransparentColor);
  77. _cursorBrush = new SolidBrush(_textNormalColor);
  78. _selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
  79. _textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
  80. _cursorPen = new Pen(_textNormalColor, cursorWidth);
  81. _selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
  82. _padPressedPen = new Pen(borderColor, _padPressedPenWidth);
  83. _inputTextFontSize = 20;
  84. CreateFonts(uiTheme.FontFamily);
  85. }
  86. private void CreateFonts(string uiThemeFontFamily)
  87. {
  88. // Try a list of fonts in case any of them is not available in the system.
  89. string[] availableFonts = new string[]
  90. {
  91. uiThemeFontFamily,
  92. "Liberation Sans",
  93. "FreeSans",
  94. "DejaVu Sans",
  95. "Lucida Grande"
  96. };
  97. foreach (string fontFamily in availableFonts)
  98. {
  99. try
  100. {
  101. _messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
  102. _inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
  103. _labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
  104. return;
  105. }
  106. catch
  107. {
  108. }
  109. }
  110. throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
  111. }
  112. private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
  113. {
  114. var a = (byte)(color.A * 255);
  115. var r = (byte)(color.R * 255);
  116. var g = (byte)(color.G * 255);
  117. var b = (byte)(color.B * 255);
  118. if (flipRgb)
  119. {
  120. r = (byte)(255 - r);
  121. g = (byte)(255 - g);
  122. b = (byte)(255 - b);
  123. }
  124. return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
  125. }
  126. private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
  127. {
  128. Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
  129. return LoadResource(resourceStream, newWidth, newHeight);
  130. }
  131. private Image LoadResource(Stream resourceStream, int newWidth, int newHeight)
  132. {
  133. Debug.Assert(resourceStream != null);
  134. var image = Image.Load(resourceStream);
  135. if (newHeight != 0 && newWidth != 0)
  136. {
  137. image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
  138. }
  139. return image;
  140. }
  141. private void SetGraphicsOptions(IImageProcessingContext context)
  142. {
  143. context.GetGraphicsOptions().Antialias = true;
  144. context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
  145. }
  146. private void DrawImmutableElements()
  147. {
  148. if (_surface == null)
  149. {
  150. return;
  151. }
  152. _surface.Mutate(context =>
  153. {
  154. SetGraphicsOptions(context);
  155. context.Clear(Color.Transparent);
  156. context.Fill(_panelBrush, _panelRectangle);
  157. context.DrawImage(_ryujinxLogo, _logoPosition, 1);
  158. float halfWidth = _panelRectangle.Width / 2;
  159. float buttonsY = _panelRectangle.Y + 185;
  160. PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
  161. DrawControllerToggle(context, disableButtonPosition);
  162. });
  163. }
  164. public void DrawMutableElements(SoftwareKeyboardUiState state)
  165. {
  166. if (_surface == null)
  167. {
  168. return;
  169. }
  170. _surface.Mutate(context =>
  171. {
  172. var messageRectangle = MeasureString(MessageText, _messageFont);
  173. float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
  174. float messagePositionY = _messagePositionY - messageRectangle.Y;
  175. var messagePosition = new PointF(messagePositionX, messagePositionY);
  176. var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
  177. SetGraphicsOptions(context);
  178. context.Fill(_panelBrush, messageBoundRectangle);
  179. context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
  180. if (!state.TypingEnabled)
  181. {
  182. // Just draw a semi-transparent rectangle on top to fade the component with the background.
  183. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
  184. context.Fill(_disabledBrush, messageBoundRectangle);
  185. }
  186. DrawTextBox(context, state);
  187. float halfWidth = _panelRectangle.Width / 2;
  188. float buttonsY = _panelRectangle.Y + 185;
  189. PointF acceptButtonPosition = new PointF(halfWidth - 180, buttonsY);
  190. PointF cancelButtonPosition = new PointF(halfWidth , buttonsY);
  191. PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
  192. DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
  193. DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
  194. });
  195. }
  196. public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
  197. {
  198. if (_surfaceInfo != null)
  199. {
  200. return;
  201. }
  202. _surfaceInfo = surfaceInfo;
  203. Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
  204. // Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
  205. // image if the pitch is different.
  206. uint totalWidth = _surfaceInfo.Pitch / 4;
  207. uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
  208. Debug.Assert(_surfaceInfo.Width <= totalWidth);
  209. Debug.Assert(_surfaceInfo.Height <= totalHeight);
  210. Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
  211. _surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
  212. ComputeConstants();
  213. DrawImmutableElements();
  214. }
  215. private void ComputeConstants()
  216. {
  217. int totalWidth = (int)_surfaceInfo.Width;
  218. int totalHeight = (int)_surfaceInfo.Height;
  219. int panelHeight = 240;
  220. int panelPositionY = totalHeight - panelHeight;
  221. _panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
  222. _messagePositionY = panelPositionY + 60;
  223. int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
  224. int logoPositionY = panelPositionY + 18;
  225. _logoPosition = new Point(logoPositionX, logoPositionY);
  226. }
  227. private static RectangleF MeasureString(string text, Font font)
  228. {
  229. RendererOptions options = new RendererOptions(font);
  230. if (text == "")
  231. {
  232. FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
  233. return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
  234. }
  235. FontRectangle rectangle = TextMeasurer.Measure(text, options);
  236. return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
  237. }
  238. private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
  239. {
  240. RendererOptions options = new RendererOptions(font);
  241. if (text == "")
  242. {
  243. FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
  244. return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
  245. }
  246. FontRectangle rectangle = TextMeasurer.Measure(text, options);
  247. return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
  248. }
  249. private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
  250. {
  251. var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
  252. float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
  253. float boxHeight = 32;
  254. float boxY = _panelRectangle.Y + 110;
  255. float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
  256. RectangleF boxRectangle = new RectangleF(boxX, boxY, boxWidth, boxHeight);
  257. RectangleF boundRectangle = new RectangleF(_panelRectangle.X, boxY - _textBoxOutlineWidth,
  258. _panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
  259. context.Fill(_panelBrush, boundRectangle);
  260. context.Draw(_textBoxOutlinePen, boxRectangle);
  261. float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
  262. float inputTextY = boxY + 5;
  263. var inputTextPosition = new PointF(inputTextX, inputTextY);
  264. context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
  265. // Draw the cursor on top of the text and redraw the text with a different color if necessary.
  266. Color cursorTextColor;
  267. IBrush cursorBrush;
  268. Pen cursorPen;
  269. float cursorPositionYTop = inputTextY + 1;
  270. float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
  271. float cursorPositionXLeft;
  272. float cursorPositionXRight;
  273. bool cursorVisible = false;
  274. if (state.CursorBegin != state.CursorEnd)
  275. {
  276. Debug.Assert(state.InputText.Length > 0);
  277. cursorTextColor = _textSelectedColor;
  278. cursorBrush = _selectionBoxBrush;
  279. cursorPen = _selectionBoxPen;
  280. ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin);
  281. ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd);
  282. var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
  283. var selectionEndRectangle = MeasureString(textUntilEnd , _inputTextFont);
  284. cursorVisible = true;
  285. cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
  286. cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
  287. }
  288. else
  289. {
  290. cursorTextColor = _textOverCursorColor;
  291. cursorBrush = _cursorBrush;
  292. cursorPen = _cursorPen;
  293. if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold)
  294. {
  295. // Show the blinking cursor.
  296. int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
  297. ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin);
  298. var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
  299. cursorVisible = true;
  300. cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
  301. if (state.OverwriteMode)
  302. {
  303. // The blinking cursor is in overwrite mode so it takes the size of a character.
  304. if (state.CursorBegin < state.InputText.Length)
  305. {
  306. textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1);
  307. cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
  308. cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
  309. }
  310. else
  311. {
  312. cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
  313. }
  314. }
  315. else
  316. {
  317. // The blinking cursor is in insert mode so it is only a line.
  318. cursorPositionXRight = cursorPositionXLeft;
  319. }
  320. }
  321. else
  322. {
  323. cursorPositionXLeft = inputTextX;
  324. cursorPositionXRight = inputTextX;
  325. }
  326. }
  327. if (state.TypingEnabled && cursorVisible)
  328. {
  329. float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
  330. float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
  331. if (cursorWidth == 0)
  332. {
  333. PointF[] points = new PointF[]
  334. {
  335. new PointF(cursorPositionXLeft, cursorPositionYTop),
  336. new PointF(cursorPositionXLeft, cursorPositionYBottom),
  337. };
  338. context.DrawLines(cursorPen, points);
  339. }
  340. else
  341. {
  342. var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
  343. context.Draw(cursorPen , cursorRectangle);
  344. context.Fill(cursorBrush, cursorRectangle);
  345. Image<Argb32> textOverCursor = new Image<Argb32>((int)cursorRectangle.Width, (int)cursorRectangle.Height);
  346. textOverCursor.Mutate(context =>
  347. {
  348. var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
  349. context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
  350. });
  351. var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
  352. context.DrawImage(textOverCursor, cursorPosition, 1);
  353. }
  354. }
  355. else if (!state.TypingEnabled)
  356. {
  357. // Just draw a semi-transparent rectangle on top to fade the component with the background.
  358. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
  359. context.Fill(_disabledBrush, boundRectangle);
  360. }
  361. }
  362. private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
  363. {
  364. // Use relative positions so we can center the the entire drawing later.
  365. float iconX = 0;
  366. float iconY = 0;
  367. float iconWidth = icon.Width;
  368. float iconHeight = icon.Height;
  369. var labelRectangle = MeasureString(label, _labelsTextFont);
  370. float labelPositionX = iconWidth + 8 - labelRectangle.X;
  371. float labelPositionY = 3;
  372. float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
  373. float fullHeight = iconHeight;
  374. // Convert all relative positions into absolute.
  375. float originX = (int)(point.X - fullWidth / 2);
  376. float originY = (int)(point.Y - fullHeight / 2);
  377. iconX += originX;
  378. iconY += originY;
  379. var iconPosition = new Point((int)iconX, (int)iconY);
  380. var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
  381. var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
  382. fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
  383. var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
  384. boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
  385. context.Fill(_panelBrush, boundRectangle);
  386. context.DrawImage(icon, iconPosition, 1);
  387. context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
  388. if (enabled)
  389. {
  390. if (pressed)
  391. {
  392. context.Draw(_padPressedPen, selectedRectangle);
  393. }
  394. }
  395. else
  396. {
  397. // Just draw a semi-transparent rectangle on top to fade the component with the background.
  398. // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
  399. context.Fill(_disabledBrush, boundRectangle);
  400. }
  401. }
  402. private void DrawControllerToggle(IImageProcessingContext context, PointF point)
  403. {
  404. var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
  405. // Use relative positions so we can center the the entire drawing later.
  406. float keyWidth = _keyModeIcon.Width;
  407. float keyHeight = _keyModeIcon.Height;
  408. float labelPositionX = keyWidth + 8 - labelRectangle.X;
  409. float labelPositionY = -labelRectangle.Y - 1;
  410. float keyX = 0;
  411. float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
  412. float fullWidth = labelPositionX + labelRectangle.Width;
  413. float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
  414. // Convert all relative positions into absolute.
  415. float originX = (int)(point.X - fullWidth / 2);
  416. float originY = (int)(point.Y - fullHeight / 2);
  417. keyX += originX;
  418. keyY += originY;
  419. var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
  420. var overlayPosition = new Point((int)keyX, (int)keyY);
  421. context.DrawImage(_keyModeIcon, overlayPosition, 1);
  422. context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
  423. }
  424. public void CopyImageToBuffer()
  425. {
  426. lock (_bufferLock)
  427. {
  428. if (_surface == null)
  429. {
  430. return;
  431. }
  432. // Convert the pixel format used in the image to the one used in the Switch surface.
  433. if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
  434. {
  435. return;
  436. }
  437. _bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
  438. Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
  439. Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
  440. for (int i = 0; i < dataConvert.Length; i++)
  441. {
  442. dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
  443. }
  444. }
  445. }
  446. public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position)
  447. {
  448. lock (_bufferLock)
  449. {
  450. if (_bufferData == null)
  451. {
  452. return false;
  453. }
  454. try
  455. {
  456. destination.Write(position, _bufferData);
  457. }
  458. catch
  459. {
  460. return false;
  461. }
  462. return true;
  463. }
  464. }
  465. }
  466. }