YSTest: DSReader.cpp 源文件

YSTest PreAlpha_b400_20130424 The YSLib Test Project

- 首页
- 相关页面
- 模块
- 命名空间
- 类
- 文件

- 文件列表
- 文件成员

全部 类 命名空间 文件 函数 变量 类型定义 枚举 枚举值 友元 宏定义 组 页
DSReader.cpp
浏览该文件的文档.
1 ﻿/*
2 Copyright by FrankHB 2010 - 2013.
3
4 This file is part of the YSLib project, and may only be used,
5 modified, and distributed under the terms of the YSLib project
6 license, LICENSE.TXT. By continuing to use, modify, or distribute
7 this file you indicate that you have read the license and
8 understand and accept it fully.
9 */
10
28 #include "DSReader.h"
29 #include <algorithm> // for std::copy_n;
30 #include <YSLib/UI/ywindow.h>
31 #include <YSLib/Service/TextLayout.h>
32
33 YSL_BEGIN
34
35 using namespace Drawing;
36 using namespace Text;
37 using ystdex::next_if_eq;
38
39 namespace
40 {
41
46 template<typename _tBi>
47 _tBi
48 FindPreviousChar(_tBi s, _tBi b, ucs4_t c = ucs4_t())
49 {
50 // while(b < --s && *s != c)
51 while(b != --s && *s != c)
52 ;
53 return s;
54 }
55
60 template<typename _tBi>
61 _tBi
62 FindLineFeed(const TextRegion& r, _tBi s, _tBi e)
63 {
64 const SDst wmax(r.GetWidth() - GetHorizontalOf(r.Margin));
65 SDst w(0);
66
67 // while(s < e && *s != '\n')
68 while(s != e && *s != '\n')
69 {
70 if(std::iswprint(*s))
71 {
72 w += r.Font.GetAdvance(*s);
73 if(w >= wmax)
74 break;
75 }
76 ++s;
77 }
78 return s;
79 }
80
85 template<typename _tBi>
86 _tBi
87 FindPreviousLineFeed(TextRegion& r, _tBi s, _tBi b)
88 {
89 // if(b < s)
90 if(b != s)
91 {
92 const auto e(s);
93 auto t(FindPreviousChar(s, b, '\n'));
94
95 do
96 {
97 s = t;
98 if(*t == '\n')
99 ++t;
100 t = FindLineFeed(r, t, e);
101 }while(t != e);
102 }
103 return s;
104 }
105
110 template<typename _tIn, class _tArea, class _tContainer>
111 inline void
112 AdjustForNewline(_tArea& area, _tIn& i, _tContainer& c)
113 {
114 i = FindLineFeed(area, next_if_eq(i, '\n'), c.GetEnd());
115 }
116
121 template<typename _tIn, class _tArea, class _tContainer>
122 inline void
123 AdjustPrevious(_tArea& area, _tIn& i, _tContainer& c)
124 {
125 i = FindPreviousLineFeed(area, i, c.GetBegin());
126 }
127
139 void
140 CopyScrollArea(YSLib::UI::BufferedTextArea& src_area,
141 size_t src_offset, YSLib::UI::BufferedTextArea& dst_area,
142 size_t dst_offset, ptrdiff_t offset, size_t n)
143 {
144 YAssert(n != 0, "Invalid number of lines found.");
145 YAssert(n <= size_t(std::abs(offset)), "Invalid offset found.");
146
147 const SDst w(src_area.GetWidth());
148
149 YAssert(w == dst_area.GetWidth(), "Distinct screen widths found.");
150
151 yunseq(src_offset *= w, dst_offset *= w, n *= w);
152 dst_area.Scroll(offset);
153 yunseq(std::copy_n(src_area.GetBufferPtr() + src_offset, n,
154 dst_area.GetBufferPtr() + dst_offset), std::copy_n(
155 src_area.GetBufferAlphaPtr() + src_offset, n,
156 dst_area.GetBufferAlphaPtr() + dst_offset));
157 src_area.Scroll(offset);
158 }
159
165 void
166 MoveScrollArea(YSLib::UI::BufferedTextArea& area_up,
167 YSLib::UI::BufferedTextArea& area_dn, ptrdiff_t offset, size_t n)
168 {
169 YAssert(area_up.GetHeight() - area_up.Margin.Bottom - n > 0,
170 "No enough space of areas found.");
171
172 SDst src_off(area_dn.Margin.Top),
173 dst_off(area_up.GetHeight() - area_up.Margin.Bottom - n);
174 auto* p_src(&area_dn);
175 auto* p_dst(&area_up);
176 SDst clr_off;
177
178 if(offset > 0) //复制区域向下移动，即浏览区域向上滚动。
179 {
180 std::swap(p_src, p_dst),
181 std::swap(src_off, dst_off),
182 clr_off = area_up.Margin.Top;
183 }
184 else
185 clr_off = area_dn.GetHeight() - area_dn.Margin.Bottom - n;
186 CopyScrollArea(*p_src, src_off, *p_dst, dst_off, offset, n);
187 p_src->ClearLine(clr_off, n);
188 }
189
191 u16
192 CheckOverRead(TextRegion& r)
193 {
194 const auto b(FetchLastLineBasePosition(r, r.GetHeight()));
195
196 return r.Pen.Y < b ? (b - r.Pen.Y) / GetTextLineHeightExOf(r) : 0;
197 }
198
199 } // unnamed namespace;
200
201 YSL_BEGIN_NAMESPACE(DS)
202
203 YSL_BEGIN_NAMESPACE(UI)
204
205 DualScreenReader::DualScreenReader(SDst w, SDst h_up, SDst h_down,
206 FontCache& fc_)
207 : p_text(), fc(fc_), i_top(), i_btm(), overread_line_n(0), scroll_offset(0),
208 Margin(4, 4, 4, 4),
209 area_up(Rect({}, w, h_up), fc), area_dn(Rect({}, w, h_down), fc)
210 {
211 SetFontSize(),
212 SetColor(),
213 SetLineGap(),
214 area_up.Background = nullptr,
215 area_dn.Background = nullptr;
216 }
217
218 void
219 DualScreenReader::SetLineGap(u8 g)
220 {
221 if(area_up.LineGap != g)
222 {
223 yunseq(area_up.LineGap = g, area_dn.LineGap = g);
224 UpdateView();
225 }
226 }
227
228 void
229 DualScreenReader::SetVisible(bool b)
230 {
231 SetVisibleOf(area_up, b), SetVisibleOf(area_dn, b);
232
233 using YSLib::UI::Invalidate;
234
235 //强制刷新背景。
236 Invalidate(area_up);
237 Invalidate(area_dn);
238 }
239
240 void
241 DualScreenReader::SetFont(const Font& fnt)
242 {
243 yunseq(area_up.Font = fnt, area_dn.Font = fnt);
244 }
245 void
246 DualScreenReader::SetFontSize(FontSize s)
247 {
248 area_up.Font.SetSize(s),
249 area_dn.Font.SetSize(s);
250 // NOTE: Margins shall be adjusted before output.
251 }
252
253 void
254 DualScreenReader::AdjustForFirstNewline()
255 {
256 AdjustForNewline(area_up, i_top, *p_text);
257 }
258
259 void
260 DualScreenReader::AdjustForPrevNewline()
261 {
262 AdjustPrevious(area_up, i_top, *p_text);
263 }
264
265 void
266 DualScreenReader::AdjustMargins()
267 {
268 yunseq(area_up.Margin = Margin, area_dn.Margin = Margin);
269 yunseq(AdjustBottomMarginOf(area_up), AdjustBottomMarginOf(area_dn));
270 {
271 const SPos v((area_up.Margin.Bottom - area_up.Margin.Top) / 2);
272
273 yunseq(area_up.Margin.Top += v, area_up.Margin.Bottom -= v);
274 }
275 {
276 const SPos v((area_dn.Margin.Bottom - area_dn.Margin.Top) / 2);
277
278 yunseq(area_dn.Margin.Top += v, area_dn.Margin.Bottom -= v);
279 }
280 }
281
282 FontSize
283 DualScreenReader::AdjustScrollOffset()
284 {
285 return bool(p_text) && scroll_offset != 0
286 ? ScrollByPixel(GetTextLineHeightExOf(area_up) - scroll_offset) : 0;
287 }
288
289 void
290 DualScreenReader::Attach(YSLib::UI::Window& wnd_up,
291 YSLib::UI::Window& wnd_dn)
292 {
293 wnd_up += area_up,
294 wnd_dn += area_dn;
295 }
296
297 void
298 DualScreenReader::Detach()
299 {
300 using YSLib::UI::Window;
301
302 if(auto pCon = dynamic_cast<Window*>(FetchContainerPtr(area_up)))
303 *pCon -= area_up;
304 if(auto pCon = dynamic_cast<Window*>(FetchContainerPtr(area_dn)))
305 *pCon -= area_dn;
306 }
307
308 bool
309 DualScreenReader::Execute(Command cmd)
310 {
311 if(YB_UNLIKELY(!p_text || p_text->GetTextSize() == 0))
312 return false;
313 if(YB_UNLIKELY(~cmd & Scroll))
314 return false;
315 if(AdjustScrollOffset() != 0)
316 return false;
317 if(cmd & Up)
318 {
319 if(YB_UNLIKELY(IsTextTop()))
320 return false;
321 }
322 else if(YB_UNLIKELY(IsTextBottom()))
323 return false;
324
325 YAssert(area_up.LineGap == area_dn.LineGap, "Distinct line gaps found.");
326 // TODO: Assert the fonts are same.
327
328 cmd &= ~Scroll;
329 if(cmd & Line)
330 {
331 const FontSize h(area_up.Font.GetHeight()), hx(h + GetLineGap());
332
333 if(cmd & Up)
334 {
335 MoveScrollArea(area_up, area_dn, hx, h);
336 SetCurrentTextLineNOf(area_up, 0);
337 AdjustForPrevNewline();
338 CarriageReturn(area_up);
339 PutLine(area_up, next_if_eq(i_top, '\n'), p_text->GetEnd(), '\n');
340 if(overread_line_n > 0)
341 --overread_line_n;
342 else
343 AdjustPrevious(area_up, i_btm, *p_text);
344 }
345 else
346 {
347 MoveUpForLastLine(-hx, h);
348 //注意缓冲区不保证以空字符结尾。
349 CarriageReturn(area_dn);
350 i_btm = PutLastLine();
351 AdjustForFirstNewline();
352 }
353 Invalidate();
354 }
355 else
356 {
357 auto ln(area_up.GetTextLineNEx() + area_dn.GetTextLineNEx());
358
359 if(cmd & Up)
360 while(ln--)
361 AdjustForPrevNewline();
362 else
363 while(ln-- && !IsTextBottom())
364 {
365 AdjustForNewline(area_dn, i_btm, *p_text);
366 AdjustForFirstNewline();
367 }
368 UpdateView();
369 }
370 return true;
371 }
372
373 void
374 DualScreenReader::Invalidate()
375 {
376 using YSLib::UI::Invalidate;
377
378 //强制刷新背景。
379 Invalidate(area_up);
380 Invalidate(area_dn);
381 if(ViewChanged)
382 ViewChanged();
383 }
384
385 void
386 DualScreenReader::Locate(size_t pos)
387 {
388 YAssert(bool(p_text), "Null text buffer found.");
389
390 const auto s(p_text->GetTextSize());
391
392 if(s == 0)
393 {
394 Reset();
395 Invalidate();
396 return;
397 }
398 if(pos == 0)
399 i_top = p_text->GetBegin();
400 else if(pos < s)
401 {
402 i_top = p_text->GetIterator(pos);
403 ++i_top;
404 AdjustForPrevNewline();
405 }
406 else
407 return;
408 UpdateView();
409 }
410
411 void
412 DualScreenReader::LoadText(TextFile& file)
413 {
414 if(YB_LIKELY(file))
415 {
416 p_text = make_unique<Text::TextFileBuffer>(file);
417 yunseq(i_top = p_text->GetBegin(), i_btm = p_text->GetEnd());
418 UpdateView();
419 }
420 else
421 {
422 UnloadText();
423 Reset();
424 PutString(area_up, u"文件打开失败！");
425 Invalidate();
426 }
427 }
428
429 void
430 DualScreenReader::MoveUpForLastLine(ptrdiff_t off, size_t h)
431 {
432 MoveScrollArea(area_up, area_dn, off, h);
433
434 u16 n(area_dn.GetTextLineNEx());
435
436 YAssert(n != 0, "No Enough height.");
437
438 SetCurrentTextLineNOf(area_dn, --n);
439 }
440
441 Text::TextFileBuffer::Iterator
442 DualScreenReader::PutLastLine()
443 {
444 return PutLine(area_dn, next_if_eq(i_btm, '\n'), p_text->GetEnd(), '\n');
445 }
446
447 void
448 DualScreenReader::Reset()
449 {
450 //清除字符区域缓冲区。
451 area_up.ClearImage();
452 area_dn.ClearImage();
453 //根据行距调整并均衡边距。
454 AdjustMargins();
455 //复位缓存区域写入位置。
456 area_up.ResetPen();
457 area_dn.ResetPen();
458 }
459
460 FontSize
461 DualScreenReader::ScrollByPixel(Drawing::FontSize h)
462 {
463 const FontSize ln_h_ex(GetTextLineHeightExOf(area_up));
464
465 YAssert(scroll_offset < ln_h_ex, "Invalid scroll offset found."),
466 YAssert(bool(p_text), "Null text buffer found.");
467
468 if(YB_UNLIKELY(i_btm == p_text->GetEnd() || scroll_offset + h > ln_h_ex))
469 return 0;
470 MoveUpForLastLine(-h, h);
471 //注意缓冲区不保证以空字符结尾。
472 CarriageReturn(area_dn);
473 if(YB_LIKELY((scroll_offset += h) < ln_h_ex))
474 {
475 area_dn.Pen.Y += ln_h_ex - scroll_offset;
476 PutLastLine();
477 }
478 else
479 {
480 i_btm = PutLastLine();
481 AdjustForFirstNewline();
482 scroll_offset = 0;
483 }
484 Invalidate();
485 return h;
486 }
487
488 void
489 DualScreenReader::Stretch(SDst h)
490 {
491 RestrictInClosedInterval(h, 0, MainScreenHeight - 40);
492 h = MainScreenHeight - h;
493
494 const SDst w(area_dn.GetWidth());
495
496 SetSizeOf(area_dn, Size(w, h)),
497 area_dn.SetSize(w, h);
498 UpdateView();
499 }
500
501 void
502 DualScreenReader::UnloadText()
503 {
504 yunseq(i_top = Text::TextFileBuffer::Iterator(),
505 i_btm = Text::TextFileBuffer::Iterator(),
506 p_text = nullptr);
507 }
508
509 void
510 DualScreenReader::UpdateView()
511 {
512 if(YB_UNLIKELY(!p_text || p_text->GetTextSize() == 0))
513 return;
514 Reset();
515 {
516 auto i_new(PutString(area_up, next_if_eq(i_top, '\n'),
517 p_text->GetEnd()));
518
519 if(YB_UNLIKELY(i_new == p_text->GetEnd()))
520 {
521 i_btm = i_new;
522 overread_line_n = CheckOverRead(area_up) + area_dn.GetTextLineNEx();
523 }
524 else
525 {
526 i_btm = PutString(area_dn, i_new, p_text->GetEnd());
527 overread_line_n = CheckOverRead(area_dn);
528 }
529 }
530 if(YB_LIKELY(!IsTextBottom() && *i_btm == '\n'))
531 --i_btm;
532 Invalidate();
533 }
534
535 YSL_END_NAMESPACE(UI)
536
537 YSL_END_NAMESPACE(DS)
538
539 YSL_END
540