daigaku 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +62 -0
- data/Rakefile +2 -0
- data/bin/daigaku +12 -0
- data/daigaku.gemspec +37 -0
- data/lib/daigaku.rb +27 -0
- data/lib/daigaku/chapter.rb +23 -0
- data/lib/daigaku/configuration.rb +86 -0
- data/lib/daigaku/course.rb +23 -0
- data/lib/daigaku/database.rb +64 -0
- data/lib/daigaku/exceptions.rb +19 -0
- data/lib/daigaku/generator.rb +53 -0
- data/lib/daigaku/loadable.rb +23 -0
- data/lib/daigaku/loading/chapters.rb +9 -0
- data/lib/daigaku/loading/courses.rb +9 -0
- data/lib/daigaku/loading/units.rb +9 -0
- data/lib/daigaku/reference_solution.rb +10 -0
- data/lib/daigaku/solution.rb +42 -0
- data/lib/daigaku/task.rb +10 -0
- data/lib/daigaku/terminal.rb +11 -0
- data/lib/daigaku/terminal/cli.rb +59 -0
- data/lib/daigaku/terminal/courses.rb +114 -0
- data/lib/daigaku/terminal/output.rb +72 -0
- data/lib/daigaku/terminal/setup.rb +115 -0
- data/lib/daigaku/terminal/solutions.rb +46 -0
- data/lib/daigaku/terminal/texts/about.txt +19 -0
- data/lib/daigaku/terminal/texts/courses_empty.txt +3 -0
- data/lib/daigaku/terminal/texts/hint_course_download.txt +13 -0
- data/lib/daigaku/terminal/texts/welcome.txt +12 -0
- data/lib/daigaku/terminal/welcome.rb +98 -0
- data/lib/daigaku/test.rb +46 -0
- data/lib/daigaku/test_result.rb +69 -0
- data/lib/daigaku/unit.rb +28 -0
- data/lib/daigaku/version.rb +3 -0
- data/lib/daigaku/views.rb +59 -0
- data/lib/daigaku/views/chapters_menu.rb +91 -0
- data/lib/daigaku/views/courses_menu.rb +87 -0
- data/lib/daigaku/views/main_menu.rb +37 -0
- data/lib/daigaku/views/splash.rb +57 -0
- data/lib/daigaku/views/task_view.rb +206 -0
- data/lib/daigaku/views/top_bar.rb +48 -0
- data/lib/daigaku/views/units_menu.rb +92 -0
- data/lib/daigaku/window.rb +160 -0
- data/spec/daigaku/chapter_spec.rb +76 -0
- data/spec/daigaku/configuration_spec.rb +161 -0
- data/spec/daigaku/course_spec.rb +75 -0
- data/spec/daigaku/database_spec.rb +79 -0
- data/spec/daigaku/generator_spec.rb +82 -0
- data/spec/daigaku/loading/chapters_spec.rb +16 -0
- data/spec/daigaku/loading/courses_spec.rb +16 -0
- data/spec/daigaku/loading/units_spec.rb +21 -0
- data/spec/daigaku/reference_solution_spec.rb +23 -0
- data/spec/daigaku/solution_spec.rb +79 -0
- data/spec/daigaku/task_spec.rb +23 -0
- data/spec/daigaku/terminal/cli_spec.rb +51 -0
- data/spec/daigaku/terminal/courses_spec.rb +60 -0
- data/spec/daigaku/terminal/output_spec.rb +123 -0
- data/spec/daigaku/terminal/setup_spec.rb +10 -0
- data/spec/daigaku/terminal/solutions_spec.rb +8 -0
- data/spec/daigaku/terminal/welcome_spec.rb +12 -0
- data/spec/daigaku/terminal_spec.rb +14 -0
- data/spec/daigaku/test_example_spec.rb +54 -0
- data/spec/daigaku/test_result_spec.rb +81 -0
- data/spec/daigaku/test_spec.rb +48 -0
- data/spec/daigaku/unit_spec.rb +85 -0
- data/spec/daigaku/views/chapters_menu_spec.rb +8 -0
- data/spec/daigaku/views/courses_menu_spec.rb +8 -0
- data/spec/daigaku/views/task_view_spec.rb +7 -0
- data/spec/daigaku/views/units_menu_spec.rb +8 -0
- data/spec/daigaku/views_spec.rb +23 -0
- data/spec/daigaku_spec.rb +57 -0
- data/spec/path_helpers_spec.rb +60 -0
- data/spec/resource_helpers_spec.rb +33 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/macros/content_helpers.rb +129 -0
- data/spec/support/macros/mock_helpers.rb +20 -0
- data/spec/support/macros/path_helpers.rb +133 -0
- data/spec/support/macros/resource_helpers.rb +119 -0
- data/spec/support/macros/test_helpers.rb +6 -0
- metadata +361 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
|
5
|
+
class CoursesMenu
|
6
|
+
include Views
|
7
|
+
include Wisper::Publisher
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@position = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def enter
|
14
|
+
@window = default_window
|
15
|
+
|
16
|
+
main_panel(@window) do |window|
|
17
|
+
show sub_window_below_top_bar(window)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def reenter_courses_menu(course)
|
22
|
+
@current_course = course
|
23
|
+
enter
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def show(window)
|
29
|
+
draw(window, @position)
|
30
|
+
interact_with(window)
|
31
|
+
end
|
32
|
+
|
33
|
+
def draw(window, active_index = 0)
|
34
|
+
window.attrset(A_NORMAL)
|
35
|
+
window.setpos(0, 1)
|
36
|
+
window.write 'Available daigaku courses:'
|
37
|
+
|
38
|
+
course_entries.each_with_index do |item, index|
|
39
|
+
window.setpos(index + 2, 1)
|
40
|
+
window.print_indicator(courses[index])
|
41
|
+
window.attrset(index == active_index ? A_STANDOUT : A_NORMAL)
|
42
|
+
window.write " #{item.to_s} "
|
43
|
+
end
|
44
|
+
|
45
|
+
window.refresh
|
46
|
+
end
|
47
|
+
|
48
|
+
def interact_with(window)
|
49
|
+
while char = window.getch
|
50
|
+
case char
|
51
|
+
when KEY_UP
|
52
|
+
@position -= 1
|
53
|
+
broadcast(:reset_menu_position)
|
54
|
+
when KEY_DOWN
|
55
|
+
@position += 1
|
56
|
+
broadcast(:reset_menu_position)
|
57
|
+
when 10 # Enter
|
58
|
+
broadcast(:enter_chapters_menu, courses[@position])
|
59
|
+
return
|
60
|
+
when 27 # ESC
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
|
64
|
+
entries = course_entries
|
65
|
+
@position = entries.length - 1 if @position < 0
|
66
|
+
@position = 0 if @position >= entries.length
|
67
|
+
draw(window, @position)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def courses
|
72
|
+
@courses = Loading::Courses.load(Daigaku.config.courses_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def course_entries
|
76
|
+
non_empty_courses = courses.select { |course| !course.chapters.empty? }
|
77
|
+
|
78
|
+
@course_entries = non_empty_courses.map do |course|
|
79
|
+
line = "#{course.title}"
|
80
|
+
line << "(#{course.author})" if course.author
|
81
|
+
line
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class MainMenu
|
5
|
+
include Views
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
courses_menu = Views::CoursesMenu.new
|
9
|
+
chapters_menu = Views::ChaptersMenu.new
|
10
|
+
units_menu = Views::UnitsMenu.new
|
11
|
+
task_view = Views::TaskView.new
|
12
|
+
|
13
|
+
# Subscription: `first.subscribe(second)` means
|
14
|
+
# first subscribes second on the first's broadcast.
|
15
|
+
# second has to have method that is broadcasted.
|
16
|
+
|
17
|
+
# top down navigation
|
18
|
+
courses_menu.subscribe(chapters_menu, on: :enter_chapters_menu)
|
19
|
+
chapters_menu.subscribe(units_menu, on: :enter_units_menu)
|
20
|
+
units_menu.subscribe(task_view, on: :enter_task_view)
|
21
|
+
|
22
|
+
# bottom up navigation
|
23
|
+
chapters_menu.subscribe(courses_menu, on: :reenter_courses_menu)
|
24
|
+
units_menu.subscribe(chapters_menu, on: :reenter_chapters_menu)
|
25
|
+
task_view.subscribe(units_menu, on: :reenter_units_menu)
|
26
|
+
|
27
|
+
# position reset
|
28
|
+
courses_menu.subscribe(chapters_menu, on: :reset_menu_position)
|
29
|
+
courses_menu.subscribe(units_menu, on: :reset_menu_position)
|
30
|
+
chapters_menu.subscribe(units_menu, on: :reset_menu_position)
|
31
|
+
|
32
|
+
courses_menu.enter
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class Splash
|
5
|
+
include Views
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
title = "DAIGAKU"
|
9
|
+
subtitle = "Learning the Ruby programming language dead easy."
|
10
|
+
|
11
|
+
panel = default_window
|
12
|
+
|
13
|
+
lines.times do |line|
|
14
|
+
panel.setpos(line, 0)
|
15
|
+
panel.red(' ' * cols, Curses::A_STANDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
panel.setpos((lines / 4), (cols - title.length) / 2)
|
19
|
+
panel.red(title, Curses::A_STANDOUT)
|
20
|
+
panel.refresh
|
21
|
+
|
22
|
+
sleep 0.5
|
23
|
+
|
24
|
+
ruby_ascii_art.each_with_index do |line, index|
|
25
|
+
panel.setpos(lines / 4 + 2 + index, (cols - line.length) / 2)
|
26
|
+
panel.red(line, Curses::A_STANDOUT)
|
27
|
+
sleep 0.06
|
28
|
+
panel.refresh
|
29
|
+
end
|
30
|
+
|
31
|
+
panel.setpos(lines / 4 + 11, (cols - subtitle.length) / 2)
|
32
|
+
|
33
|
+
subtitle.chars do |char|
|
34
|
+
panel.red(char, Curses::A_STANDOUT)
|
35
|
+
panel.refresh
|
36
|
+
sleep 0.02
|
37
|
+
end
|
38
|
+
|
39
|
+
sleep 2.5
|
40
|
+
|
41
|
+
close_screen
|
42
|
+
end
|
43
|
+
|
44
|
+
def ruby_ascii_art
|
45
|
+
[
|
46
|
+
" ___________ ",
|
47
|
+
" /.\\ /.\\ /.\\ ",
|
48
|
+
"/___\\/___\\/___\\",
|
49
|
+
" \\ \\ . / . / ",
|
50
|
+
" \\ \\ ./ ./ ",
|
51
|
+
" \\\\ / / ",
|
52
|
+
" \\./ "
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
require 'os'
|
5
|
+
|
6
|
+
class TaskView
|
7
|
+
include Views
|
8
|
+
include Wisper::Publisher
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@lines = []
|
12
|
+
@top = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def enter_task_view(course, chapter, unit)
|
16
|
+
@course = course
|
17
|
+
@chapter = chapter
|
18
|
+
@unit = unit
|
19
|
+
|
20
|
+
@test_result_lines = nil
|
21
|
+
@lines = @unit.task.markdown.lines
|
22
|
+
@top_bar_height = 4
|
23
|
+
@head_height = 2
|
24
|
+
|
25
|
+
initialize_window(@lines.count + @top_bar_height + @head_height)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def set_head(window)
|
31
|
+
window.setpos(0, 1)
|
32
|
+
window.clear_line
|
33
|
+
window.emphasize @course.title
|
34
|
+
window.write ' > '
|
35
|
+
window.emphasize @chapter.title
|
36
|
+
window.write ' > '
|
37
|
+
window.emphasize @unit.title
|
38
|
+
window.write ':'
|
39
|
+
|
40
|
+
window.setpos(1, 1)
|
41
|
+
window.clear_line
|
42
|
+
end
|
43
|
+
|
44
|
+
def show(window)
|
45
|
+
window.scrollok(true)
|
46
|
+
draw(window)
|
47
|
+
interact_with(window)
|
48
|
+
end
|
49
|
+
|
50
|
+
def draw(window)
|
51
|
+
@top = 0
|
52
|
+
window.attrset(A_NORMAL)
|
53
|
+
set_head(window)
|
54
|
+
|
55
|
+
@lines.each_with_index do |line, index|
|
56
|
+
window.setpos(index + 2, 1)
|
57
|
+
print_line(window, line, index)
|
58
|
+
end
|
59
|
+
|
60
|
+
window.setpos(0, 1)
|
61
|
+
window.refresh
|
62
|
+
end
|
63
|
+
|
64
|
+
def scroll_up(window)
|
65
|
+
if @top > 0
|
66
|
+
window.scrl(-1)
|
67
|
+
set_head(window)
|
68
|
+
|
69
|
+
@top -= 1
|
70
|
+
line = @lines[@top]
|
71
|
+
|
72
|
+
if line
|
73
|
+
window.setpos(2, 1)
|
74
|
+
print_line(window, line, @top)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def scroll_down(window)
|
80
|
+
if @top + Curses.lines <= @lines.count + @top_bar_height + @head_height
|
81
|
+
window.scrl(1)
|
82
|
+
set_head(window)
|
83
|
+
|
84
|
+
@top += 1
|
85
|
+
line = @lines[@top + window.maxy - 1]
|
86
|
+
window.setpos(window.maxy - 1, 1)
|
87
|
+
|
88
|
+
if line
|
89
|
+
print_line(window, line, @top)
|
90
|
+
else
|
91
|
+
window.clear_line
|
92
|
+
end
|
93
|
+
|
94
|
+
return true
|
95
|
+
else
|
96
|
+
return false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def print_line(window, line, index)
|
101
|
+
if @test_result_lines && index.between?(0, @test_result_lines.count + 1)
|
102
|
+
if @unit.mastered?
|
103
|
+
window.green(line, A_STANDOUT, full_line: true)
|
104
|
+
else
|
105
|
+
example_index = ((index + 1) / 4.0).ceil - 1
|
106
|
+
|
107
|
+
if @examples[example_index].passed?
|
108
|
+
window.green(line, A_STANDOUT, full_line: true)
|
109
|
+
else
|
110
|
+
window.red(line, A_STANDOUT, full_line: true)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
window.print_markdown(line.strip)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def interact_with(window)
|
119
|
+
while char = window.getch
|
120
|
+
scrollable = true
|
121
|
+
|
122
|
+
case char
|
123
|
+
when 'v' # Verifiy
|
124
|
+
print_test_results(window)
|
125
|
+
return
|
126
|
+
when 'c' # clear
|
127
|
+
reset_screen(window)
|
128
|
+
return
|
129
|
+
when 'o' # open solution file
|
130
|
+
open_editor(@unit.solution.path)
|
131
|
+
when Curses::KEY_DOWN, Curses::KEY_CTRL_N
|
132
|
+
scrollable = scroll_down(window)
|
133
|
+
when Curses::KEY_UP, Curses::KEY_CTRL_P
|
134
|
+
scrollable = scroll_up(window)
|
135
|
+
when Curses::KEY_NPAGE, ?\s # white space
|
136
|
+
0.upto(window.maxy - 2) do |n|
|
137
|
+
scrolled = scroll_down(window)
|
138
|
+
|
139
|
+
unless scrolled
|
140
|
+
scrollable = false if n == 0
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
when Curses::KEY_PPAGE
|
145
|
+
0.upto(window.maxy - 2) do |n|
|
146
|
+
scrolled = scroll_up(window)
|
147
|
+
|
148
|
+
unless scrolled
|
149
|
+
scrollable = false if n == 0
|
150
|
+
break
|
151
|
+
end
|
152
|
+
end
|
153
|
+
when Curses::KEY_LEFT, Curses::KEY_CTRL_T
|
154
|
+
while scroll_up(window)
|
155
|
+
end
|
156
|
+
when Curses::KEY_RIGHT, Curses::KEY_CTRL_B
|
157
|
+
while scroll_down(window)
|
158
|
+
end
|
159
|
+
when Curses::KEY_BACKSPACE
|
160
|
+
broadcast(:reenter_units_menu, @course, @chapter, @unit)
|
161
|
+
return
|
162
|
+
when 27 # ESC
|
163
|
+
exit
|
164
|
+
end
|
165
|
+
|
166
|
+
Curses.beep unless scrollable
|
167
|
+
window.setpos(0, 1)
|
168
|
+
window.refresh
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def open_editor(file_path)
|
173
|
+
if OS.windows?
|
174
|
+
system "start '' '#{file_path}'"
|
175
|
+
elsif OS.mac?
|
176
|
+
system "open '#{file_path}'"
|
177
|
+
elsif OS.linux?
|
178
|
+
system "xdg-open '#{file_path}'"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def print_test_results(window)
|
183
|
+
result = @unit.solution.verify!
|
184
|
+
|
185
|
+
@test_result_lines = result.summary.strip.split("\n")
|
186
|
+
@lines = [''] + @test_result_lines + ['', ''] + @unit.task.markdown.lines
|
187
|
+
@examples = result.examples
|
188
|
+
|
189
|
+
initialize_window(@lines.count + @top_bar_height + @head_height)
|
190
|
+
end
|
191
|
+
|
192
|
+
def reset_screen(window)
|
193
|
+
@test_result_lines = nil
|
194
|
+
@lines = @unit.task.markdown.lines
|
195
|
+
height = [@lines.count + @top_bar_height + @head_height, Curses.lines].max
|
196
|
+
initialize_window(height)
|
197
|
+
end
|
198
|
+
|
199
|
+
def initialize_window(height)
|
200
|
+
@window = default_window(height)
|
201
|
+
main_panel(@window) { |window| show sub_window_below_top_bar(window) }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class TopBar
|
5
|
+
include Curses
|
6
|
+
|
7
|
+
attr_reader :height, :width, :panel
|
8
|
+
|
9
|
+
def initialize(window)
|
10
|
+
@height = 4
|
11
|
+
@width = window.maxx
|
12
|
+
@panel = create_panel(window, @width, @height)
|
13
|
+
end
|
14
|
+
|
15
|
+
def show
|
16
|
+
@panel.refresh
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def create_panel(window, width, heigth)
|
22
|
+
panel = window.subwin(heigth, window.maxx, 0, 0)
|
23
|
+
|
24
|
+
panel.setpos(1, 1)
|
25
|
+
panel.write 'Use '
|
26
|
+
panel.emphasize 'UP KEY'
|
27
|
+
panel.write ' and '
|
28
|
+
panel.emphasize 'DOWN KEY'
|
29
|
+
panel.write ' for menu navigation'
|
30
|
+
panel.write ' | Enter menu with '
|
31
|
+
panel.emphasize 'RETURN'
|
32
|
+
panel.write ' | Go back with '
|
33
|
+
panel.emphasize 'BACKSPACE'
|
34
|
+
panel.write ' | Exit with '
|
35
|
+
panel.emphasize 'ESC'
|
36
|
+
panel.setpos(2, 1)
|
37
|
+
panel.write '_' * (window.maxx - 3)
|
38
|
+
|
39
|
+
panel
|
40
|
+
end
|
41
|
+
|
42
|
+
def emphasized(panel, text)
|
43
|
+
panel.write(text, Window::COLOR_2)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Daigaku
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
|
5
|
+
class UnitsMenu
|
6
|
+
include Views
|
7
|
+
include Wisper::Publisher
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@position = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def enter_units_menu(course, chapter)
|
14
|
+
@window = default_window
|
15
|
+
@course = course
|
16
|
+
@chapter = chapter
|
17
|
+
|
18
|
+
main_panel(@window) do |window|
|
19
|
+
show sub_window_below_top_bar(window)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def reenter_units_menu(course, chapter, unit)
|
24
|
+
@course = course
|
25
|
+
@chapter = chapter
|
26
|
+
|
27
|
+
@position = chapter.units.find_index(unit)
|
28
|
+
enter_units_menu(@course, @chapter)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def show(window)
|
34
|
+
draw(window, @position)
|
35
|
+
interact_with(window)
|
36
|
+
end
|
37
|
+
|
38
|
+
def draw(window, active_index = 0)
|
39
|
+
window.attrset(A_NORMAL)
|
40
|
+
window.setpos(0, 1)
|
41
|
+
window.emphasize @course.title
|
42
|
+
window.write ' > '
|
43
|
+
window.emphasize @chapter.title
|
44
|
+
window.write ' > - available units:'
|
45
|
+
|
46
|
+
menu_items.each_with_index do |item, index|
|
47
|
+
window.setpos(index + 2, 1)
|
48
|
+
window.print_indicator(units[index])
|
49
|
+
window.attrset(index == active_index ? A_STANDOUT : A_NORMAL)
|
50
|
+
window.write " #{item.to_s} "
|
51
|
+
end
|
52
|
+
|
53
|
+
window.refresh
|
54
|
+
end
|
55
|
+
|
56
|
+
def interact_with(window)
|
57
|
+
while char = window.getch
|
58
|
+
case char
|
59
|
+
when KEY_UP
|
60
|
+
@position -= 1
|
61
|
+
when KEY_DOWN
|
62
|
+
@position += 1
|
63
|
+
when 10 # Enter
|
64
|
+
broadcast(:enter_task_view, @course, @chapter, units[@position])
|
65
|
+
return
|
66
|
+
when 263 # Backspace
|
67
|
+
broadcast(:reenter_chapters_menu, @course, @chapter)
|
68
|
+
return
|
69
|
+
when 27 # ESC
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
@position = menu_items.length - 1 if @position < 0
|
74
|
+
@position = 0 if @position >= menu_items.length
|
75
|
+
draw(window, @position)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def units
|
80
|
+
@chapter.units
|
81
|
+
end
|
82
|
+
|
83
|
+
def menu_items
|
84
|
+
@menu_items = units.map do |unit|
|
85
|
+
unit.title
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|