Add basic methods into NavigatedPage

This commit is contained in:
2026-08-13 22:03:47 +03:00
parent d8e7cbbc36
commit 4ca7aa7044
17 changed files with 590 additions and 324 deletions
+41 -39
View File
@@ -102,30 +102,8 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
fn navigated(&mut self) {
}
}
impl DictionaryState {
pub fn new(state: Arc<Mutex<AppState>>) -> Self {
let len = state.lock().unwrap().dictionary.len();
let mut result = DictionaryState {
include_map: vec![false; len],
selected_group_index: 0,
state,
tag_map: HashMap::new(),
reverse: false,
search: "".to_string(),
no_typing: true,
reverse_list: true,
auto_save_queue: HashMap::new(),
};
result.update_tags();
result
}
pub fn update(&mut self, message: DictionaryMessage) -> Task<RootMessage> {
fn update(&mut self, message: DictionaryMessage) -> Task<RootMessage> {
match message {
NewWord => {
let mut state = self.state.lock().unwrap();
@@ -271,6 +249,45 @@ impl DictionaryState {
Task::none()
}
fn view(&self) -> iced::Element<'_, DictionaryMessage> {
back_overlay(
row![
iced::widget::column![
self.groups_panel(),
row![horizontal().width(8), self.words_list(),],
button("Добавить слово").style(jl_button).on_press(NewWord),
]
.spacing(5),
self.filters(),
]
.spacing(DEFAULT_SPACING)
.into(),
Back,
)
}
}
impl DictionaryState {
pub fn new(state: Arc<Mutex<AppState>>) -> Self {
let len = state.lock().unwrap().dictionary.len();
let mut result = DictionaryState {
include_map: vec![false; len],
selected_group_index: 0,
state,
tag_map: HashMap::new(),
reverse: false,
search: "".to_string(),
no_typing: true,
reverse_list: true,
auto_save_queue: HashMap::new(),
};
result.update_tags();
result
}
fn save_word(&mut self, i: usize) {
let state = &mut self.state.lock().unwrap();
@@ -291,22 +308,7 @@ impl DictionaryState {
)
}
pub fn view(&self) -> iced::Element<'_, DictionaryMessage> {
back_overlay(
row![
iced::widget::column![
self.groups_panel(),
row![horizontal().width(8), self.words_list(),],
button("Добавить слово").style(jl_button).on_press(NewWord),
]
.spacing(5),
self.filters(),
]
.spacing(DEFAULT_SPACING)
.into(),
Back,
)
}
fn words_list(&self) -> iced::Element<'_, DictionaryMessage> {
let time = Instant::now();
+26 -24
View File
@@ -44,25 +44,8 @@ impl NavigatedPage<DictionaryQuizMessage> for DictionaryQuizState {
fn navigated(&mut self) {
}
}
impl DictionaryQuizState {
pub fn new(words: Vec<WordData>, reverse: bool, no_typing: bool) -> DictionaryQuizState {
DictionaryQuizState {
words,
current_set: Vec::new(),
answer: "".to_string(),
view: "---".to_string(),
correct: "".to_string(),
score: Default::default(),
is_help: false,
reverse,
laps: 0,
no_typing,
}
}
pub fn update(&mut self, message: DictionaryQuizMessage) -> Task<RootMessage> {
fn update(&mut self, message: DictionaryQuizMessage) -> Task<RootMessage> {
match message {
Back => {}
AnswerChanged(c) => self.answer = c.clone(),
@@ -72,7 +55,7 @@ impl DictionaryQuizState {
Task::none()
}
pub fn view(&self) -> Element<'_, DictionaryQuizMessage> {
fn view(&self) -> Element<'_, DictionaryQuizMessage> {
container(
iced::widget::column![
self.laps(),
@@ -112,13 +95,32 @@ impl DictionaryQuizState {
]
.spacing(DEFAULT_SPACING),
]
.spacing(DEFAULT_SPACING)
.align_x(alignment::Horizontal::Center),
.spacing(DEFAULT_SPACING)
.align_x(alignment::Horizontal::Center),
)
.center_y(Fill)
.center_x(Fill)
.into()
.center_y(Fill)
.center_x(Fill)
.into()
}
}
impl DictionaryQuizState {
pub fn new(words: Vec<WordData>, reverse: bool, no_typing: bool) -> DictionaryQuizState {
DictionaryQuizState {
words,
current_set: Vec::new(),
answer: "".to_string(),
view: "---".to_string(),
correct: "".to_string(),
score: Default::default(),
is_help: false,
reverse,
laps: 0,
no_typing,
}
}
fn submit(&mut self) {
if self.view == "---" {
self.show_next();
+19 -18
View File
@@ -25,6 +25,25 @@ impl NavigatedPage<HistoryMessage> for HistoryState {
}
fn navigated(&mut self) {
}
fn update(&mut self, _: HistoryMessage) -> Task<RootMessage> {
Task::none()
}
fn view(&self) -> Element<'_, HistoryMessage> {
back_overlay(
iced::widget::row![
horizontal().width(FillPortion(1)),
scrollable(self.history_lines().padding(DEFAULT_SPACING))
.height(Fill)
.width(FillPortion(5)),
horizontal().width(FillPortion(1))
]
.height(Fill)
.width(Fill)
.into(),
Back,
)
}
}
impl HistoryState {
@@ -49,25 +68,7 @@ impl HistoryState {
}
}
pub fn update(&mut self, _: HistoryMessage) -> Task<RootMessage> {
Task::none()
}
pub fn view(&self) -> Element<'_, HistoryMessage> {
back_overlay(
iced::widget::row![
horizontal().width(FillPortion(1)),
scrollable(self.history_lines().padding(DEFAULT_SPACING))
.height(Fill)
.width(FillPortion(5)),
horizontal().width(FillPortion(1))
]
.height(Fill)
.width(Fill)
.into(),
Back,
)
}
fn history_lines(&self) -> Column<'_, HistoryMessage> {
let mut column = Column::new();
+28
View File
@@ -0,0 +1,28 @@
use iced::{Element, Task};
use crate::navigation::{NavigatedPage, Page, RootMessage};
struct ImportState {}
enum ImportMessage {
Back
}
impl NavigatedPage<ImportMessage> for ImportState {
fn navigate(&self, message: &ImportMessage) -> Option<Page> {
if let ImportMessage::Back = message {
return Some(Page::PreviousPage)
}
None
}
fn navigated(&mut self) {
}
fn update(&mut self, message: ImportMessage) -> Task<RootMessage> {
todo!()
}
fn view(&self) -> Element<'_, ImportMessage> {
todo!()
}
}
+1
View File
@@ -15,6 +15,7 @@ mod sync;
mod word;
mod writing;
mod repetition_settings;
pub mod import;
use crate::data_provider::card_sets::load_sets;
use crate::data_provider::settings::get_setting;
+2
View File
@@ -320,6 +320,8 @@ macro_rules! state_navigate_handle {
pub trait NavigatedPage<T> {
fn navigate(&self, message: &T) -> Option<Page>;
fn navigated(&mut self);
fn update(&mut self, message: T) -> Task<RootMessage>;
fn view(&self) -> Element<'_, T>;
}
pub trait KeyPressedPage {
+50 -48
View File
@@ -30,34 +30,8 @@ impl NavigatedPage<QuizMessage> for QuizState {
fn navigated(&mut self) {
}
}
impl QuizState {
pub(crate) fn new() -> QuizState {
QuizState {
kana: "".to_string(),
correct_roman: "a".to_string(),
is_help: false,
current_roman: "".to_string(),
set: KanaSet::hiragana(),
queue: Vec::new(),
score: Score {
total: 0,
correct: 0,
fail: 0,
},
}
}
}
impl Default for QuizState {
fn default() -> Self {
QuizState::new()
}
}
impl QuizState {
pub fn update(&mut self, message: QuizMessage) -> Task<RootMessage> {
fn update(&mut self, message: QuizMessage) -> Task<RootMessage> {
match message {
ContentChanged(content) => {
if content.contains("`") {
@@ -86,24 +60,9 @@ impl QuizState {
Task::none()
}
fn update_showed(&mut self) {
self.current_roman = String::new();
let queue = &mut self.queue;
if queue.is_empty() {
for pair in self.set.list() {
queue.push(pair);
}
queue.shuffle(&mut rand::rng());
}
let pair = self.queue.pop().unwrap();
self.kana = pair.0;
self.correct_roman = pair.1;
}
pub fn view(&self) -> Element<'_, QuizMessage> {
fn view(&self) -> Element<'_, QuizMessage> {
container(
iced::widget::column![
row![
@@ -137,12 +96,55 @@ impl QuizState {
.spacing(DEFAULT_SPACING),
button("Закончить").style(jl_button).on_press(Back),
]
.spacing(DEFAULT_SPACING)
.align_x(alignment::Horizontal::Center),
.spacing(DEFAULT_SPACING)
.align_x(alignment::Horizontal::Center),
)
.center_y(Fill)
.center_x(Fill)
.into()
.center_y(Fill)
.center_x(Fill)
.into()
}
}
impl QuizState {
pub(crate) fn new() -> QuizState {
QuizState {
kana: "".to_string(),
correct_roman: "a".to_string(),
is_help: false,
current_roman: "".to_string(),
set: KanaSet::hiragana(),
queue: Vec::new(),
score: Score {
total: 0,
correct: 0,
fail: 0,
},
}
}
}
impl Default for QuizState {
fn default() -> Self {
QuizState::new()
}
}
impl QuizState {
fn update_showed(&mut self) {
self.current_roman = String::new();
let queue = &mut self.queue;
if queue.is_empty() {
for pair in self.set.list() {
queue.push(pair);
}
queue.shuffle(&mut rand::rng());
}
let pair = self.queue.pop().unwrap();
self.kana = pair.0;
self.correct_roman = pair.1;
}
}
#[derive(Debug, Clone)]
+20 -20
View File
@@ -29,23 +29,7 @@ impl NavigatedPage<RandomizerMessage> for RandomizerState {
fn navigated(&mut self) {
}
}
impl Default for RandomizerState {
fn default() -> Self {
Self::new()
}
}
impl RandomizerState {
pub fn new() -> RandomizerState {
RandomizerState {
text: Default::default(),
list: vec![],
}
}
pub fn update(&mut self, message: RandomizerMessage) -> Task<RootMessage> {
fn update(&mut self, message: RandomizerMessage) -> Task<RootMessage> {
match message {
Edit(action) => {
self.text.perform(action);
@@ -65,7 +49,7 @@ impl RandomizerState {
Task::none()
}
pub fn view(&self) -> iced::Element<'_, RandomizerMessage> {
fn view(&self) -> iced::Element<'_, RandomizerMessage> {
back_overlay(
iced::widget::column![
text_editor(&self.text)
@@ -75,9 +59,25 @@ impl RandomizerState {
.placeholder("Каждый элемент с новой строки"),
button("Перемешать").style(jl_button).on_press(Start),
]
.spacing(HALF_SPACING)
.into(),
.spacing(HALF_SPACING)
.into(),
Back,
)
}}
impl Default for RandomizerState {
fn default() -> Self {
Self::new()
}
}
impl RandomizerState {
pub fn new() -> RandomizerState {
RandomizerState {
text: Default::default(),
list: vec![],
}
}
}
+56 -54
View File
@@ -39,30 +39,7 @@ impl NavigatedPage<RepetitionMessage> for RepetitionState {
fn navigated(&mut self) {
}
}
impl RepetitionState {
pub(crate) fn new(set: CardSetSettings, state: Arc<Mutex<AppState>>) -> RepetitionState {
let mut card_set = CardSet::new(&set, state.clone());
let (word, stat) = card_set.next();
let sink_handle = rodio::DeviceSinkBuilder::open_default_sink().unwrap();
RepetitionState {
state,
settings: set,
set: card_set,
current_word: word,
current_statistic: stat,
open: false,
can_play: true,
sink: Arc::new(sink_handle),
opened: HashSet::new(),
}
}
}
impl RepetitionState {
pub fn update(&mut self, message: RepetitionMessage) -> Task<RootMessage> {
fn update(&mut self, message: RepetitionMessage) -> Task<RootMessage> {
match message {
RepetitionMessage::Back => {}
RepetitionMessage::Next => return self.next(),
@@ -87,6 +64,60 @@ impl RepetitionState {
Task::none()
}
fn view(&self) -> Element<'_, RepetitionMessage> {
back_overlay(
column![
container(self.draw_forward())
.width(Fill)
.height(Fill)
.align_x(Center)
.align_y(alignment::Vertical::Center),
rule::horizontal(2),
container(self.draw_backward())
.width(Fill)
.height(Fill)
.align_x(Center)
.align_y(alignment::Vertical::Center),
container(self.answer_bar())
.width(Fill)
.align_x(Center)
.height(80),
text!(
"Затронуто слов {}, {}%",
self.opened.len(),
(self.opened.len() as f32 / self.set.len() as f32 * 10000.0).round() / 100.0
)
]
.height(Fill)
.width(Fill)
.into(),
RepetitionMessage::Back,
)
}
}
impl RepetitionState {
pub(crate) fn new(set: CardSetSettings, state: Arc<Mutex<AppState>>) -> RepetitionState {
let mut card_set = CardSet::new(&set, state.clone());
let (word, stat) = card_set.next();
let sink_handle = rodio::DeviceSinkBuilder::open_default_sink().unwrap();
RepetitionState {
state,
settings: set,
set: card_set,
current_word: word,
current_statistic: stat,
open: false,
can_play: true,
sink: Arc::new(sink_handle),
opened: HashSet::new(),
}
}
}
impl RepetitionState {
fn next(&mut self) -> Task<RootMessage> {
if self.open {
@@ -137,36 +168,7 @@ impl RepetitionState {
state.activity.insert(id, vec![(now, 1)]);
}
}
pub fn view(&self) -> Element<'_, RepetitionMessage> {
back_overlay(
column![
container(self.draw_forward())
.width(Fill)
.height(Fill)
.align_x(Center)
.align_y(alignment::Vertical::Center),
rule::horizontal(2),
container(self.draw_backward())
.width(Fill)
.height(Fill)
.align_x(Center)
.align_y(alignment::Vertical::Center),
container(self.answer_bar())
.width(Fill)
.align_x(Center)
.height(80),
text!(
"Затронуто слов {}, {}%",
self.opened.len(),
(self.opened.len() as f32 / self.set.len() as f32 * 10000.0).round() / 100.0
)
]
.height(Fill)
.width(Fill)
.into(),
RepetitionMessage::Back,
)
}
fn draw_forward(&self) -> Element<'_, RepetitionMessage> {
self.draw_card_view(self.settings.forward.as_str())
+8 -7
View File
@@ -47,10 +47,7 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
fn navigated(&mut self) {
}
}
impl RepetitionSettingsState {
pub fn update(&mut self, message: RepetitionSettingsMessage) -> Task<RootMessage> {
fn update(&mut self, message: RepetitionSettingsMessage) -> Task<RootMessage> {
match message {
Back => {}
Save => {
@@ -93,7 +90,7 @@ impl RepetitionSettingsState {
Task::none()
}
pub fn view(&self) -> Element<'_, RepetitionSettingsMessage> {
fn view(&self) -> Element<'_, RepetitionSettingsMessage> {
back_overlay(
iced::widget::column![
scrollable(
@@ -145,11 +142,15 @@ impl RepetitionSettingsState {
]
.spacing(DEFAULT_SPACING),
]
.spacing(DEFAULT_SPACING)
.into(),
.spacing(DEFAULT_SPACING)
.into(),
Back,
)
}
}
impl RepetitionSettingsState {
fn count_view(&self, set: &CardSetSettings) -> Element<'_, RepetitionSettingsMessage> {
if let Some(count) = set.count {
+22 -21
View File
@@ -59,21 +59,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
fn navigated(&mut self) {
self.selected_set = None;
}
}
impl RepetitionsState {
pub(crate) fn new(state: Arc<Mutex<AppState>>) -> RepetitionsState {
let count = state.lock().unwrap().card_sets.len();
RepetitionsState {
selected_set: None,
correct_filters: vec![true; count],
state,
}
}
}
impl RepetitionsState {
pub fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> {
fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> {
let mut state = self.state.lock().unwrap();
match message {
Next => {}
@@ -139,7 +125,7 @@ impl RepetitionsState {
Task::none()
}
pub fn view(&self) -> Element<'_, RepetitionsMessage> {
fn view(&self) -> Element<'_, RepetitionsMessage> {
back_overlay(
row![
column![
@@ -153,15 +139,30 @@ impl RepetitionsState {
.width(Length::FillPortion(1)),
self.selected_set_view(),
]
.align_y(Center)
.spacing(DEFAULT_SPACING)
.width(Fill)
.height(Fill)
.into(),
.align_y(Center)
.spacing(DEFAULT_SPACING)
.width(Fill)
.height(Fill)
.into(),
Back,
)
}
}
impl RepetitionsState {
pub(crate) fn new(state: Arc<Mutex<AppState>>) -> RepetitionsState {
let count = state.lock().unwrap().card_sets.len();
RepetitionsState {
selected_set: None,
correct_filters: vec![true; count],
state,
}
}
}
impl RepetitionsState {
fn launch_delete_button(&self, set: &CardSetSettings) -> Element<'_, RepetitionsMessage> {
if set.id != 0 {
return row![
+29 -27
View File
@@ -64,17 +64,7 @@ impl NavigatedPage<SelectorMessage> for SelectorState {
fn navigated(&mut self) {
}
}
impl SelectorState {
pub fn new(state: Arc<Mutex<AppState>>) -> Self {
Self {
set: Default::default(),
is_writing: false,
state,
}
}
pub fn update(&mut self, message: SelectorMessage) -> Task<RootMessage> {
fn update(&mut self, message: SelectorMessage) -> Task<RootMessage> {
match message {
SelectorMessage::Change => match self.set.chars_type {
KanaType::Katakana => self.set = KanaSet::hiragana(),
@@ -86,19 +76,7 @@ impl SelectorState {
}
Task::none()
}
fn nav_style(theme: &Theme, status: Status) -> Style {
let mut basic = button::text(theme, status);
basic.background = if status == Status::Hovered {
Some(Background::Color(Color::WHITE.scale_alpha(0.1)))
} else {
None
};
basic.border.radius = BUTTON_RADIUS.into();
basic
}
pub fn view(&self) -> Element<'_, SelectorMessage> {
fn view(&self) -> Element<'_, SelectorMessage> {
container(
iced::widget::column![
row![
@@ -127,11 +105,35 @@ impl SelectorState {
.on_press(SelectorMessage::Goto)
.style(cta_button),
]
.spacing(DEFAULT_SPACING),
.spacing(DEFAULT_SPACING),
)
.padding(10)
.into()
.padding(10)
.into()
}
}
impl SelectorState {
pub fn new(state: Arc<Mutex<AppState>>) -> Self {
Self {
set: Default::default(),
is_writing: false,
state,
}
}
fn nav_style(theme: &Theme, status: Status) -> Style {
let mut basic = button::text(theme, status);
basic.background = if status == Status::Hovered {
Some(Background::Color(Color::WHITE.scale_alpha(0.1)))
} else {
None
};
basic.border.radius = BUTTON_RADIUS.into();
basic
}
fn rows_selector(&self) -> Element<'_, SelectorMessage> {
let mut row = Row::new();
+19 -18
View File
@@ -49,23 +49,7 @@ impl NavigatedPage<SyncMessage> for SyncState {
}
fn navigated(&mut self) {}
}
impl SyncState {
pub fn new(state: Arc<Mutex<AppState>>) -> SyncState {
let auto_fetch;
{
auto_fetch = state.lock().unwrap().sync_data.auto_web_fetch;
}
Self {
auto_fetch,
state,
frozen: false,
progress: 0.0,
}
}
pub fn update(&mut self, message: SyncMessage) -> Task<RootMessage> {
fn update(&mut self, message: SyncMessage) -> Task<RootMessage> {
match message {
Back => {}
CopyKey => {
@@ -158,7 +142,7 @@ impl SyncState {
Task::none()
}
pub fn view(&self) -> Element<'_, SyncMessage> {
fn view(&self) -> Element<'_, SyncMessage> {
back_overlay(
row![space().width(Fill), self.sync_column(), space().width(Fill)]
.spacing(DEFAULT_SPACING)
@@ -167,6 +151,23 @@ impl SyncState {
Back,
)
}
}
impl SyncState {
pub fn new(state: Arc<Mutex<AppState>>) -> SyncState {
let auto_fetch;
{
auto_fetch = state.lock().unwrap().sync_data.auto_web_fetch;
}
Self {
auto_fetch,
state,
frozen: false,
progress: 0.0,
}
}
fn sync_column(&self) -> Element<'_, SyncMessage> {
let network_view: Element<'_, SyncMessage> = if self.frozen {
+14 -13
View File
@@ -28,16 +28,7 @@ impl NavigatedPage<WordMessage> for WordState {
fn navigated(&mut self) {
}
}
impl WordState {
pub(crate) fn new(word: WordData, index: usize, state: Arc<Mutex<AppState>>) -> WordState {
WordState { state, index, word }
}
}
impl WordState {
pub fn update(&mut self, message: WordMessage) -> Task<RootMessage> {
fn update(&mut self, message: WordMessage) -> Task<RootMessage> {
match message {
Back => {}
Save => {
@@ -74,7 +65,7 @@ impl WordState {
Task::none()
}
pub fn view(&self) -> Element<'_, WordMessage> {
fn view(&self) -> Element<'_, WordMessage> {
let mut fast_add = row![];
if !self.word.additional.contains_key("reading") {
fast_add = fast_add.push(
@@ -132,12 +123,22 @@ impl WordState {
]
.spacing(DEFAULT_SPACING)
]
.spacing(DEFAULT_SPACING)
.into(),
.spacing(DEFAULT_SPACING)
.into(),
Back,
)
}
}
impl WordState {
pub(crate) fn new(word: WordData, index: usize, state: Arc<Mutex<AppState>>) -> WordState {
WordState { state, index, word }
}
}
impl WordState {
fn get_view_for_more(&self, value: (&String, &String)) -> Element<'_, WordMessage> {
match value.0.as_str() {
"reading" => self.reading_field(value),
+37 -35
View File
@@ -29,6 +29,41 @@ impl NavigatedPage<WritingMessage> for WritingState {
fn navigated(&mut self) {
}
fn update(&mut self, message: WritingMessage) -> Task<RootMessage> {
match message {
WritingMessage::Back => todo!(),
WritingMessage::Next => self.next(),
WritingMessage::SwitchShowMode(b) => self.show_all = b,
}
Task::none()
}
fn view(&self) -> Element<'_, WritingMessage> {
container(
iced::widget::column![
checkbox(self.show_all)
.label("Показывать все сразу")
.on_toggle(WritingMessage::SwitchShowMode),
text!("{}", self.roman_total).size(34),
text!("{}", self.kana).size(48),
self.answers(),
row![
button(text!("{}", self.next_text))
.style(jl_button)
.on_press(WritingMessage::Next),
button("Закончить")
.style(jl_button)
.on_press(WritingMessage::Back),
]
.spacing(DEFAULT_SPACING)
]
.spacing(DEFAULT_SPACING)
.align_x(alignment::Horizontal::Center),
)
.center_x(Fill)
.center_y(Fill)
.padding(10)
.into()
}
}
impl WritingState {
@@ -48,14 +83,7 @@ impl WritingState {
}
impl WritingState {
pub fn update(&mut self, message: WritingMessage) -> Task<RootMessage> {
match message {
WritingMessage::Back => todo!(),
WritingMessage::Next => self.next(),
WritingMessage::SwitchShowMode(b) => self.show_all = b,
}
Task::none()
}
fn next(&mut self) {
if self.set.is_empty() {
@@ -85,33 +113,7 @@ impl WritingState {
}
}
pub fn view(&self) -> Element<'_, WritingMessage> {
container(
iced::widget::column![
checkbox(self.show_all)
.label("Показывать все сразу")
.on_toggle(WritingMessage::SwitchShowMode),
text!("{}", self.roman_total).size(34),
text!("{}", self.kana).size(48),
self.answers(),
row![
button(text!("{}", self.next_text))
.style(jl_button)
.on_press(WritingMessage::Next),
button("Закончить")
.style(jl_button)
.on_press(WritingMessage::Back),
]
.spacing(DEFAULT_SPACING)
]
.spacing(DEFAULT_SPACING)
.align_x(alignment::Horizontal::Center),
)
.center_x(Fill)
.center_y(Fill)
.padding(10)
.into()
}
fn answers(&self) -> Element<'_, WritingMessage> {
if self.set.is_empty() {