Skip to content

Commit 8a47618

Browse files
committed
Merge branch 'seminars/add-materials'
2 parents 658aa46 + 119d2f9 commit 8a47618

26 files changed

+1179
-2
lines changed

ch16/graphical_user_interfaces.tex

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
% !TEX encoding = UTF8
2+
% !TEX spellcheck = ru_RU
3+
% !TEX root = ../seminars.tex
4+
5+
%%===============================================
6+
\chapter{Графические пользовательские интерфейсы}
7+
%%===============================================
8+
9+
%%====================================
10+
\section{Окно с кнопкой \texttt{Quit}}
11+
%%====================================
12+
Выполним упражнение~1 из~\textbookref{главы~16} учебника. Для~этого пронаследуем \code{My\_window} от~класса \code{Simple\_window} из~библиотеки \code{Graph\_lib}. Затем добавим по~аналогии с~кнопкой \code{Next} кнопку \code{Quit}, как показано на~рисунке~\ref{fig:mywindow}.
13+
14+
\begin{figure}[ht]
15+
{\centering
16+
\includegraphics[width=0.6\textwidth]{images/my_window.png}
17+
18+
}
19+
\caption{Простое окно \code{My\_window} с кнопкой \code{Quit}}
20+
\label{fig:mywindow}
21+
\end{figure}
22+
23+
Из~документации к~библиотеке \name{FLTK} известно, что если все окна становятся скрытыми, то цикл обработки событий прекращает работу. То есть для~реализации функции \code{quit()} можно воспользоваться методом \code{hide()}.
24+
25+
\cppfile[firstline=11, lastline=21]{projects/ch16/chessboard/board.h}
26+
27+
Конструктор нашего окна принимает те же аргументы, что и \code{Simple\_window}. Мы добавляем инициализацию кнопки \code{Quit}. Нужно сместить её вниз на~высоту кнопки \code{Next}, которую добавляет базовый класс, а также связать её с~функцией-обработчиком \code{cb\_quit()}:
28+
29+
\cppfile[firstline=7, lastline=12]{projects/ch16/chessboard/board.cpp}
30+
31+
\textbf{NB!} Обратим внимание на одну деталь, которую нам пришлось изменить в~изначальной версии кода библиотеки \code{Graph\_lib}:
32+
33+
\cppfile[firstline=8, lastline=14]{projects/lib/Graph_lib/GUI.cpp}
34+
35+
\noindent В~строке~12 мы передаём указатель на~текущий объект класса (\code{this}), то есть указатель на~кнопку (\code{Button}), а не~указатель на~окно, как это ожидается в~примерах обработчиков, показаных в~\textbookref{главе~16} учебника.
36+
37+
Отметим, что здесь мы можем передавать, вообще говоря, любые данные. Позже библиотека \name{FLTK} вернёт нам указатель на~эти данные в~функцию обратного вызова:
38+
39+
\cppfile[firstline=14, lastline=18]{projects/ch16/chessboard/board.cpp}
40+
41+
\noindent Параметр \code{widget} как раз и есть тот самый адрес кнопки, который мы передаём при~вызове функции \code{Button::attach()}, когда связываем кнопку с~окном:
42+
43+
\cppfile[firstline=11, lastline=11]{projects/ch16/chessboard/board.cpp}
44+
45+
Оказывается, во многих задачах удобнее иметь указатель на~наш виджет. А, так как он хранит адрес окна, с~которым связан, мы можем получить доступ к~нашему окну и, в~конце концов, вызвать метод \code{quit()}:
46+
47+
\cppfile[firstline=17, lastline=17]{projects/ch16/chessboard/board.cpp}
48+
49+
\noindent Здесь использован оператор \code{dynamic\_cast}, который выполняет приведение типа от~ссылки на~объект базового класса к~ссылке на~объект производного класса (\textenglish{down cast}). Такое преобразование в~общем случае небезопасно. Данный оператор выполняет проверку во~время выполнения программы и возбуждает исключение \code{std::bad\_cast} в~случае ошибки.
50+
51+
52+
53+
%%=======================
54+
\section{Шахматная доска}
55+
%%=======================
56+
Покажем, как можно создать клеточное поле и взаимодействовать с~ним на~примере упражнения~2 из~\textbookref{главы~16}. Эти идеи можно использовать для~создания практически любой игры с~клеточным полем: шашек, шахмат, сапёра, морского боя, пять в ряд и других.
57+
58+
Учитывая последующую доработку, код удобно сразу распределить между~несколькими файлами, как это предлагается в~таблице~\ref{tab:chessboard}.
59+
60+
\begin{table}[ht]
61+
{\centering\begin{tabular}{ll}
62+
\toprule
63+
\code{board.h} & класс \code{Chessboard} для~шахматной доски, а также \code{My\_window} \\
64+
\code{board.cpp} & \\[0.5em]
65+
66+
\code{cell.h} & класс \code{Cell} для~шахматной клетки \\
67+
\code{cell.cpp} & \\[0.5em]
68+
69+
\code{main.cpp} & \\
70+
\bottomrule
71+
\end{tabular}
72+
73+
}
74+
\medskip
75+
\caption{Распределение кода <<шахматной доски>> между файлами}
76+
\label{tab:chessboard}
77+
\end{table}
78+
79+
Размеры клеток и, соответственно, окна зафиксируем. Впоследствии, такое поведение можно изменить. Клетки представим квадратными кнопками и будем хранить их в~\code{Vector\_ref}. Метки строк и столбцов доски можно нарисовать при~помощи объектов \code{Marks}. Окно, согласно заданию, пронаследуем от~\code{My\_window} из~предыдущего упражнения.
80+
81+
\begin{figure}[ht]
82+
{\centering
83+
\includegraphics[width=0.5\textwidth]{images/chessboard.png}
84+
85+
}
86+
\caption{Окно с шахматной доской \(4\times 4\)}
87+
\label{fig:chessboard}
88+
\end{figure}
89+
90+
Итак, давайте разбираться по~порядку.
91+
92+
93+
94+
%%===========================
95+
\paragraph{Шахматная клетка.}
96+
%%===========================
97+
Прежде всего, создадим шахматную клетку "--- класс \code{Cell}. Клетки на~шахматной доске могут быть только белыми или чёрными. Это свойство мы выразим, используя перечисление \code{Cell::Type}. Размер клетки зафиксируем на~этапе компиляции. Тогда разумно сделать его константой класса (\code{static}), чтобы обращаться из~любой точки программы без~указания объекта \code{Cell::size}.
98+
99+
Нам придётся перекрыть метод \code{attach()}. Почему?
100+
101+
Также добавим пару методов \code{activate()}/\code{deactivate()} для~управления подсветкой активной клетки (клетка~\code{b2} на~рисунке~\ref{fig:chessboard}), то есть той клетки, которую выбрал мышкой пользователь.
102+
103+
В~заголовочном файле \code{cell.h} разместим определение класса:
104+
105+
\cppfile[firstline=8, lastline=32]{projects/ch16/chessboard/cell.h}
106+
107+
Реализация методов проста и не~требует дополнительных пояснений, кроме того, что \code{pw} "--- это защищённый член класса \code{Graph\_lib::Widget}, связанный с~реальным графическим виджетом из~\name{FLTK}.
108+
109+
Код разместим в~файле \code{cell.cpp}:
110+
111+
\cppfile[firstline=5, lastline=32]{projects/ch16/chessboard/cell.cpp}
112+
113+
114+
115+
%%==========================
116+
\paragraph{Шахматная доска.}
117+
%%==========================
118+
Основные моменты обозначены выше. Отметим здесь, что размер доски (\(N\times N\)) фиксируем на~этапе компиляции. Введём дополнительную константу \(N_{max}\), чтобы пользователь класса не~превышал определённый размер. Дело в~том, что позже мы будем добавлять подписи клеток и пока рассчитываем дизайн максимально на~8--9 клеток по~высоте. Дальше придётся что-то придумывать или каким-то образом выравнивать текстовые метки, состоящие из~двух цифр. (Это оставляем на~подумать.)
119+
120+
В~заголовочный файл \code{board.h} добавим определение класса:
121+
122+
\cppfile[firstline=23, lastline=51]{projects/ch16/chessboard/board.h}
123+
124+
Реализацию методов класса добавим в~файл \code{board.cpp}.
125+
126+
Конструктор задаёт размеры окна, создаёт клетки, располагая их в~соответствующих позициях, и рисует метки строк и столбцов доски. Зафиксируем размеры окна, чтобы пользователь не~смог его растягивать или сжимать. Сделать это позволяет функция \code{size\_range()} "--- метод окна \name{FLTK}.
127+
128+
\cppfile[firstline=48, lastline=67]{projects/ch16/chessboard/board.cpp}
129+
\cpp/ .../
130+
\cppfile[firstline=79, lastline=79]{projects/ch16/chessboard/board.cpp}
131+
132+
Цвет клетки (или тип) вычисляется на~основе номеров строки и столбца, определяющих её положение на~доске. Клетка в~левом нижнем углу имеет чёрный цвет. Обратим внимание, что клетки добавлялись в~линейный массив по~порядку в~соответствии с~направлением снизу вверх и слева направо, как принято в~шахматах.
133+
134+
\cppfile[firstline=20, lastline=26]{projects/ch16/chessboard/board.cpp}
135+
136+
137+
138+
%%=========================
139+
\paragraph{Подписи клеток.}
140+
%%=========================
141+
Метки \code{Marks} допускают всего лишь один символ, поэтому мы используем ограничение \code{N\_max} для~максимального размера доски. Если потребуется вывести двузначные номера клеток, придётся разработать новый класс или добавить метки при~помощи неименованных объектов \code{Text}.
142+
143+
А пока воспользуемся простой реализацией (добавляем код в конструктор):
144+
145+
\cppfile[firstline=67, lastline=78]{projects/ch16/chessboard/board.cpp}
146+
147+
\noindent и парой внешних вспомогательных функций:
148+
149+
\cppfile[firstline=28, lastline=46]{projects/ch16/chessboard/board.cpp}
150+
151+
\noindent Таким образом, получается вид, как на~рисунке~\ref{fig:chessboard}.
152+
153+
154+
155+
%%=========================================
156+
\paragraph{Взаимодействие с пользователем.}
157+
%%=========================================
158+
В~функцию-обработчик нажатия на~кнопку-клетку добавим простую логику самой шахматной доски. Можно:
159+
\begin{itemize}
160+
\item выделить любую клетку;
161+
\item переместить выделение, выбрав другую клетку;
162+
\item снять выделение, нажав на~выделенную клетку повторно.
163+
\end{itemize}
164+
165+
\noindent Реализация относительно проста, однако, здесь удобно использовать переменную-указатель \code{selected}. (Указатели будут обсуждаться в~\textbookref{главе~17} учебника.) Мы запоминаем адрес <<активной>> клетки в~переменной \code{selected}. Значение \code{nullptr} говорит, что <<активная>> клетка не~выбрана.
166+
167+
\cppfile[firstline=81, lastline=103]{projects/ch16/chessboard/board.cpp}
168+
169+
\noindent\textbf{NB!} Если в~отображении виджета что-то изменилось, то, скорее всего, потребуется перерисовать его вручную. Для~этого используйте \code{Fl::redraw()}.
170+
171+
Теперь настало время собрать и запустить программу целиком. Добавьте необходимые заголовки, функцию \code{main()}. Создайте окно \code{Chessboard} и запустите цикл обработки событий:
172+
173+
\cppfile[firstline=17, lastline=18]{projects/ch16/chessboard/main.cpp}
174+
175+
176+
177+
%%===============================
178+
\section{Добавление фигур. Шашки}
179+
%%===============================
180+
181+
\begin{figure}[ht]
182+
{\centering
183+
\includegraphics[width=0.5\textwidth]{images/checkers.png}
184+
185+
}
186+
\caption{Шахматная доска \(4\times 4\) с~шашками}
187+
\label{fig:checkers}
188+
\end{figure}
189+
190+
\todo{Описание будет добавлено позже.}
191+
192+
193+
194+
%%================
195+
\WhatToReadSection
196+
%%================
197+
\textcite{Stroustrup:2016:ru}: \textbf{глава~17}
198+
199+
200+
201+
%%===============
202+
\ExercisesSection
203+
%%===============
204+
\begin{exercise}
205+
\item Выполните упражнения из~\textbookref{главы~16} учебника.
206+
207+
\end{exercise}

images/checkers.png

10.7 KB
Loading

images/chessboard.png

10.1 KB
Loading

images/my_window.png

5.78 KB
Loading

projects/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ add_subdirectory(ch13/hexagon_tile)
2525
add_subdirectory(ch14/logic_shapes)
2626

2727
add_subdirectory(ch15/least_squares)
28+
29+
add_subdirectory(ch16/chessboard)
30+
add_subdirectory(ch16/checkers)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
set(TARGET "checkers")
4+
5+
set(HEADERS
6+
board.h
7+
cell.h
8+
checkers.h
9+
figure.h
10+
utils.h
11+
${LIB_DIR}/Graph_lib/fltk.h
12+
${LIB_DIR}/Graph_lib/Graph.h
13+
${LIB_DIR}/Graph_lib/GUI.h
14+
${LIB_DIR}/Graph_lib/Point.h
15+
${LIB_DIR}/Graph_lib/Simple_window.h
16+
${LIB_DIR}/Graph_lib/Window.h
17+
)
18+
set(SOURCES
19+
main.cpp
20+
board.cpp
21+
cell.cpp
22+
checkers.cpp
23+
figure.cpp
24+
${LIB_DIR}/Graph_lib/Graph.cpp
25+
${LIB_DIR}/Graph_lib/GUI.cpp
26+
${LIB_DIR}/Graph_lib/Window.cpp
27+
)
28+
29+
project(${TARGET} CXX)
30+
31+
set(FLTK_SKIP_FLUID True)
32+
set(FLTK_SKIP_FORMS True)
33+
34+
find_package(FLTK 1.3.8 EXACT REQUIRED)
35+
find_package(OpenGL REQUIRED)
36+
37+
include_directories(SYSTEM ${FLTK_INCLUDE_DIR})
38+
link_directories(${FLTK_INCLUDE_DIR}/../lib)
39+
40+
add_executable(${TARGET} ${HEADERS} ${SOURCES})
41+
42+
target_link_libraries(${TARGET} ${FLTK_LIBRARIES} ${OPENGL_LIBRARIES})
43+
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
44+
target_link_libraries(${TARGET} fltk_jpeg fltk_png fltk_z)
45+
endif()
46+
47+
install(TARGETS ${TARGET})

projects/ch16/checkers/Makefile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
TARGET = checkers
2+
3+
LIB_DIR = ../../../lib
4+
5+
SOURCES = $(wildcard ${LIB_DIR}/Graph_lib/*.cpp) \
6+
board.cpp \
7+
cell.cpp \
8+
checkers.cpp \
9+
main.cpp
10+
11+
OBJECTS = $(patsubst %.cpp,%.o,$(SOURCES))
12+
13+
14+
CXX ?= g++
15+
CXXFLAGS := -std=c++17 -pedantic -Wall -Wextra -I${LIB_DIR} \
16+
$(patsubst -I%,-isystem%,$(shell fltk-config --use-images --cxxflags))
17+
LDFLAGS := $(shell fltk-config --use-images --ldflags)
18+
19+
20+
.DEFAULT_GOAL := all
21+
.PHONY: all clean clean-all
22+
23+
24+
$(TARGET): $(OBJECTS)
25+
$(CXX) -o $@ $(OBJECTS) $(LDFLAGS)
26+
27+
%.o: %.cpp %.h
28+
$(CXX) -c $(CXXFLAGS) -o $@ $<
29+
30+
31+
all: $(TARGET)
32+
33+
34+
clean:
35+
rm -f $(OBJECTS)
36+
37+
clean-all: clean
38+
rm -f $(TARGET)
39+

0 commit comments

Comments
 (0)