Remove a level of indirection in main window example

We already have the `Action` enum that abstracts key and mouse event bindings; on top of that, putting each action handler in its own functions seems excessive.
This commit is contained in:
Mads Marquart
2024-03-01 09:22:24 +01:00
parent 72482048a0
commit 1edf4f1238

View File

@@ -15,7 +15,7 @@ use rwh_05::HasRawDisplayHandle;
use softbuffer::{Context, Surface};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{DeviceEvent, DeviceId, Event, Ime, WindowEvent};
use winit::event::{DeviceEvent, DeviceId, ElementState, Event, Ime, KeyEvent, WindowEvent};
use winit::event::{MouseButton, MouseScrollDelta};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
@@ -51,7 +51,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
});
let mut state = Application::new(&event_loop);
let mut app = Application::new(&event_loop);
event_loop.run(move |event, event_loop| match event {
Event::NewEvents(_) => (),
@@ -59,23 +59,22 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("Resumed the event loop");
// Create initial window.
state
.create_window(event_loop, None)
app.create_window(event_loop, None)
.expect("failed to create initial window");
state.print_help();
app.print_help();
}
Event::AboutToWait => {
if state.windows.is_empty() {
if app.windows.is_empty() {
println!("No windows left, exiting...");
event_loop.exit();
}
}
Event::WindowEvent { window_id, event } => {
state.handle_window_event(event_loop, window_id, event)
app.handle_window_event(event_loop, window_id, event)
}
Event::DeviceEvent { device_id, event } => {
state.handle_device_event(event_loop, device_id, event)
app.handle_device_event(event_loop, device_id, event)
}
Event::UserEvent(event) => {
println!("User event: {event:?}");
@@ -172,16 +171,62 @@ impl Application {
window.recognize_rotation_gesture(true);
}
let window_state = WindowState::new(self, window)?;
let window_id = window_state.window.id();
#[cfg(not(any(android_platform, ios_platform)))]
let surface = {
// SAFETY: the surface is dropped before the `window` which
// provided it with handle, thus it doesn't outlive it.
let mut surface = unsafe { Surface::new(&self.context, &window)? };
let size = window.inner_size();
if let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
surface
.resize(width, height)
.expect("failed to resize inner buffer");
};
surface
};
let theme = window.theme().unwrap_or(Theme::Dark);
println!("Theme: {theme:?}");
let named_idx = 0;
window.set_cursor(CURSORS[named_idx]);
// Allow IME out of the box.
let ime = true;
window.set_ime_allowed(ime);
let state = WindowState {
#[cfg(macos_platform)]
option_as_alt: window.option_as_alt(),
window,
custom_idx: self.custom_cursors.len() - 1,
cursor_grab: CursorGrabMode::None,
named_idx,
#[cfg(not(any(android_platform, ios_platform)))]
surface,
theme,
ime,
cursor_position: Default::default(),
cursor_hidden: Default::default(),
modifiers: Default::default(),
occluded: Default::default(),
rotated: Default::default(),
zoom: Default::default(),
};
let window_id = state.window.id();
println!("Created new window with id={window_id:?}");
self.windows.insert(window_id, window_state);
self.windows.insert(window_id, state);
Ok(window_id)
}
fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
// let cursor_position = self.cursor_position;
let window = self.windows.get_mut(&window_id).unwrap();
let state = self.windows.get_mut(&window_id).unwrap();
let window = &state.window;
println!("Executing action: {action:?}");
match action {
Action::CloseWindow => {
@@ -189,7 +234,7 @@ impl Application {
}
Action::CreateNewWindow => {
#[cfg(any(x11_platform, wayland_platform))]
if let Err(err) = window.window.request_activation_token() {
if let Err(err) = window.request_activation_token() {
println!("Failed to get activation token: {err}");
} else {
return;
@@ -199,26 +244,146 @@ impl Application {
eprintln!("Error creating new window: {err}");
}
}
Action::ToggleResizeIncrements => window.toggle_resize_increments(),
Action::ToggleCursorVisibility => window.toggle_cursor_visibility(),
Action::ToggleResizable => window.toggle_resizable(),
Action::ToggleDecorations => window.toggle_decorations(),
Action::ToggleFullscreen => window.toggle_fullscreen(),
Action::ToggleMaximize => window.toggle_maximize(),
Action::ToggleImeInput => window.toggle_ime(),
Action::Minimize => window.minimize(),
Action::NextCursor => window.next_cursor(),
Action::NextCustomCursor => window.next_custom_cursor(&self.custom_cursors),
Action::CycleCursorGrab => window.cycle_cursor_grab(),
Action::DragWindow => window.drag_window(),
Action::DragResizeWindow => window.drag_resize_window(),
Action::ShowWindowMenu => window.show_menu(),
Action::ToggleResizeIncrements => {
let new_increments = match window.resize_increments() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
println!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Action::ToggleCursorVisibility => {
state.cursor_hidden = !state.cursor_hidden;
window.set_cursor_visible(!state.cursor_hidden);
}
Action::ToggleResizable => {
let resizable = window.is_resizable();
window.set_resizable(!resizable);
}
Action::ToggleDecorations => {
let decorated = window.is_decorated();
window.set_decorations(!decorated);
}
Action::ToggleFullscreen => {
let fullscreen = if window.fullscreen().is_some() {
None
} else {
Some(Fullscreen::Borderless(None))
};
window.set_fullscreen(fullscreen);
}
Action::ToggleMaximize => {
let maximized = window.is_maximized();
window.set_maximized(!maximized);
}
Action::ToggleImeInput => {
state.ime = !state.ime;
window.set_ime_allowed(state.ime);
if let Some(position) = state.ime.then_some(state.cursor_position).flatten() {
window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
}
}
Action::Minimize => {
window.set_minimized(true);
}
Action::NextCursor => {
// Pick the next cursor
state.named_idx = (state.named_idx + 1) % CURSORS.len();
println!("Setting cursor to \"{:?}\"", CURSORS[state.named_idx]);
window.set_cursor(Cursor::Icon(CURSORS[state.named_idx]));
}
Action::NextCustomCursor => {
state.custom_idx = (state.custom_idx + 1) % self.custom_cursors.len();
let cursor = Cursor::Custom(self.custom_cursors[state.custom_idx].clone());
window.set_cursor(cursor);
}
Action::CycleCursorGrab => {
state.cursor_grab = match state.cursor_grab {
CursorGrabMode::None => CursorGrabMode::Confined,
CursorGrabMode::Confined => CursorGrabMode::Locked,
CursorGrabMode::Locked => CursorGrabMode::None,
};
println!("Changing cursor grab mode to {:?}", state.cursor_grab);
if let Err(err) = window.set_cursor_grab(state.cursor_grab) {
eprintln!("Error setting cursor grab: {err}");
}
}
Action::DragWindow => {
if let Err(err) = window.drag_window() {
println!("Error starting window drag: {err}");
} else {
println!("Dragging window Window={:?}", window.id());
}
}
Action::DragResizeWindow => {
let position = match state.cursor_position {
Some(position) => position,
None => {
println!("Drag-resize requires cursor to be inside the window");
return;
}
};
let win_size = window.inner_size();
let border_size = BORDER_SIZE * window.scale_factor();
let x_direction = if position.x < border_size {
ResizeDirection::West
} else if position.x > (win_size.width as f64 - border_size) {
ResizeDirection::East
} else {
// Use arbitrary direction instead of None for simplicity.
ResizeDirection::SouthEast
};
let y_direction = if position.y < border_size {
ResizeDirection::North
} else if position.y > (win_size.height as f64 - border_size) {
ResizeDirection::South
} else {
// Use arbitrary direction instead of None for simplicity.
ResizeDirection::SouthEast
};
let direction = match (x_direction, y_direction) {
(ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest,
(ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest,
(ResizeDirection::West, _) => ResizeDirection::West,
(ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast,
(ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast,
(ResizeDirection::East, _) => ResizeDirection::East,
(_, ResizeDirection::South) => ResizeDirection::South,
(_, ResizeDirection::North) => ResizeDirection::North,
_ => return,
};
if let Err(err) = window.drag_resize_window(direction) {
println!("Error starting window drag-resize: {err}");
} else {
println!("Drag-resizing window Window={:?}", window.id());
}
}
Action::ShowWindowMenu => {
if let Some(position) = state.cursor_position {
window.show_window_menu(position);
}
}
Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
Action::CycleOptionAsAlt => {
state.option_as_alt = match state.option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Setting option as alt {:?}", state.option_as_alt);
window.set_option_as_alt(state.option_as_alt);
}
#[cfg(macos_platform)]
Action::CreateNewTab => {
let tab_id = window.window.tabbing_identifier();
let tab_id = window.tabbing_identifier();
if let Err(err) = self.create_window(event_loop, Some(tab_id)) {
eprintln!("Error creating new window: {err}");
}
@@ -232,14 +397,28 @@ impl Application {
window_id: WindowId,
event: WindowEvent,
) {
let window = match self.windows.get_mut(&window_id) {
Some(window) => window,
let state = match self.windows.get_mut(&window_id) {
Some(state) => state,
None => return,
};
let window = &state.window;
match event {
WindowEvent::Resized(size) => {
window.resize(size);
// Resize the surface to the new size
WindowEvent::Resized(_size) => {
#[cfg(not(any(android_platform, ios_platform)))]
{
let (width, height) =
match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) {
(Some(width), Some(height)) => (width, height),
_ => return,
};
state
.surface
.resize(width, height)
.expect("failed to resize inner buffer");
}
window.request_redraw();
}
WindowEvent::Focused(focused) => {
if focused {
@@ -253,23 +432,51 @@ impl Application {
}
WindowEvent::ThemeChanged(theme) => {
println!("Theme changed to {theme:?}");
window.set_theme(theme);
state.theme = theme;
window.request_redraw();
}
#[cfg(not(any(android_platform, ios_platform)))]
WindowEvent::RedrawRequested => {
if let Err(err) = window.draw() {
eprintln!("Error drawing window: {err}");
// Draw the window contents.
if state.occluded {
println!("Skipping drawing occluded window={:?}", window_id);
}
const WHITE: u32 = 0xFFFFFFFF;
const DARK_GRAY: u32 = 0xFF181818;
let color = match state.theme {
Theme::Light => WHITE,
Theme::Dark => DARK_GRAY,
};
let mut buffer = state
.surface
.buffer_mut()
.expect("could not retrieve buffer");
buffer.fill(color);
window.pre_present_notify();
buffer.present().expect("failed presenting to window");
}
#[cfg(any(android_platform, ios_platform))]
WindowEvent::RedrawRequested => {
println!("Drawing but without rendering...");
}
// Change window occlusion state.
WindowEvent::Occluded(occluded) => {
window.set_occluded(occluded);
state.occluded = occluded;
if !occluded {
window.request_redraw();
}
}
WindowEvent::CloseRequested => {
println!("Closing Window={window_id:?}");
self.windows.remove(&window_id);
}
WindowEvent::ModifiersChanged(modifiers) => {
window.modifiers = modifiers.state();
println!("Modifiers changed to {:?}", window.modifiers);
state.modifiers = modifiers.state();
println!("Modifiers changed to {:?}", state.modifiers);
}
WindowEvent::MouseWheel { delta, .. } => match delta {
MouseScrollDelta::LineDelta(x, y) => {
@@ -280,42 +487,44 @@ impl Application {
}
},
WindowEvent::KeyboardInput {
event,
event:
KeyEvent {
// Dispatch actions only on press.
state: ElementState::Pressed,
logical_key,
..
},
is_synthetic: false,
..
} => {
let mods = window.modifiers;
// Dispatch actions only on press.
if event.state.is_pressed() {
let action = if let Key::Character(ch) = event.logical_key.as_ref() {
Self::process_key_binding(&ch.to_uppercase(), &mods)
} else {
None
};
if let Some(action) = action {
if let Key::Character(ch) = logical_key.as_ref() {
let mods = state.modifiers;
if let Some(action) = Self::process_key_binding(&ch.to_uppercase(), &mods) {
self.handle_action(event_loop, window_id, action);
}
}
}
WindowEvent::MouseInput { button, state, .. } => {
let mods = window.modifiers;
if let Some(action) = state
.is_pressed()
.then(|| Self::process_mouse_binding(button, &mods))
.flatten()
{
WindowEvent::KeyboardInput { .. } => {}
WindowEvent::MouseInput {
button,
state: ElementState::Pressed,
..
} => {
if let Some(action) = Self::process_mouse_binding(button, &state.modifiers) {
self.handle_action(event_loop, window_id, action);
}
}
WindowEvent::MouseInput { .. } => {}
WindowEvent::CursorLeft { .. } => {
println!("Cursor left Window={window_id:?}");
window.cursor_left();
state.cursor_position = None;
}
WindowEvent::CursorMoved { position, .. } => {
println!("Moved cursor to {position:?}");
window.cursor_moved(position);
state.cursor_position = Some(position);
if state.ime {
window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
}
}
WindowEvent::ActivationTokenDone { token: _token, .. } => {
#[cfg(any(x11_platform, wayland_platform))]
@@ -337,8 +546,8 @@ impl Application {
Ime::Disabled => println!("IME disabled for Window={window_id:?}"),
},
WindowEvent::PinchGesture { delta, .. } => {
window.zoom += delta;
let zoom = window.zoom;
state.zoom += delta;
let zoom = state.zoom;
if delta > 0.0 {
println!("Zoomed in {delta:.5} (now: {zoom:.5})");
} else {
@@ -346,8 +555,8 @@ impl Application {
}
}
WindowEvent::RotationGesture { delta, .. } => {
window.rotated += delta;
let rotated = window.rotated;
state.rotated += delta;
let rotated = state.rotated;
if delta > 0.0 {
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
} else {
@@ -359,7 +568,6 @@ impl Application {
}
WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled
| WindowEvent::KeyboardInput { .. }
| WindowEvent::CursorEntered { .. }
| WindowEvent::AxisMotion { .. }
| WindowEvent::DroppedFile(_)
@@ -416,8 +624,10 @@ impl Application {
}
}
/// State of the window.
/// Extra state on a window used in this example.
struct WindowState {
/// The actual Winit window.
window: Window,
/// IME input.
ime: bool,
/// Render surface.
@@ -425,8 +635,6 @@ struct WindowState {
/// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(any(android_platform, ios_platform)))]
surface: Surface,
/// The actual winit Window.
window: Window,
/// The window theme we're drawing with.
theme: Theme,
/// Cursor position over the window.
@@ -451,282 +659,6 @@ struct WindowState {
cursor_hidden: bool,
}
impl WindowState {
fn new(application: &Application, window: Window) -> Result<Self, Box<dyn Error>> {
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it.
#[cfg(not(any(android_platform, ios_platform)))]
let surface = unsafe { Surface::new(&application.context, &window)? };
let theme = window.theme().unwrap_or(Theme::Dark);
println!("Theme: {theme:?}");
let named_idx = 0;
window.set_cursor(CURSORS[named_idx]);
// Allow IME out of the box.
let ime = true;
window.set_ime_allowed(ime);
let size = window.inner_size();
let mut state = Self {
#[cfg(macos_platform)]
option_as_alt: window.option_as_alt(),
custom_idx: application.custom_cursors.len() - 1,
cursor_grab: CursorGrabMode::None,
named_idx,
#[cfg(not(any(android_platform, ios_platform)))]
surface,
window,
theme,
ime,
cursor_position: Default::default(),
cursor_hidden: Default::default(),
modifiers: Default::default(),
occluded: Default::default(),
rotated: Default::default(),
zoom: Default::default(),
};
state.resize(size);
Ok(state)
}
pub fn toggle_ime(&mut self) {
self.ime = !self.ime;
self.window.set_ime_allowed(self.ime);
if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
self.window
.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
}
}
pub fn minimize(&mut self) {
self.window.set_minimized(true);
}
pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
self.cursor_position = Some(position);
if self.ime {
self.window
.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
}
}
pub fn cursor_left(&mut self) {
self.cursor_position = None;
}
/// Toggle maximized.
fn toggle_maximize(&self) {
let maximized = self.window.is_maximized();
self.window.set_maximized(!maximized);
}
/// Toggle window decorations.
fn toggle_decorations(&self) {
let decorated = self.window.is_decorated();
self.window.set_decorations(!decorated);
}
/// Toggle window resizable state.
fn toggle_resizable(&self) {
let resizable = self.window.is_resizable();
self.window.set_resizable(!resizable);
}
/// Toggle cursor visibility
fn toggle_cursor_visibility(&mut self) {
self.cursor_hidden = !self.cursor_hidden;
self.window.set_cursor_visible(!self.cursor_hidden);
}
/// Toggle resize increments on a window.
fn toggle_resize_increments(&mut self) {
let new_increments = match self.window.resize_increments() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
println!("Had increments: {}", new_increments.is_none());
self.window.set_resize_increments(new_increments);
}
/// Toggle fullscreen.
fn toggle_fullscreen(&self) {
let fullscreen = if self.window.fullscreen().is_some() {
None
} else {
Some(Fullscreen::Borderless(None))
};
self.window.set_fullscreen(fullscreen);
}
/// Cycle through the grab modes ignoring errors.
fn cycle_cursor_grab(&mut self) {
self.cursor_grab = match self.cursor_grab {
CursorGrabMode::None => CursorGrabMode::Confined,
CursorGrabMode::Confined => CursorGrabMode::Locked,
CursorGrabMode::Locked => CursorGrabMode::None,
};
println!("Changing cursor grab mode to {:?}", self.cursor_grab);
if let Err(err) = self.window.set_cursor_grab(self.cursor_grab) {
eprintln!("Error setting cursor grab: {err}");
}
}
#[cfg(macos_platform)]
fn cycle_option_as_alt(&mut self) {
self.option_as_alt = match self.option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Setting option as alt {:?}", self.option_as_alt);
self.window.set_option_as_alt(self.option_as_alt);
}
/// Pick the next cursor.
fn next_cursor(&mut self) {
self.named_idx = (self.named_idx + 1) % CURSORS.len();
println!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]);
self.window
.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
}
/// Pick the next custom cursor.
fn next_custom_cursor(&mut self, custom_cursors: &[CustomCursor]) {
self.custom_idx = (self.custom_idx + 1) % custom_cursors.len();
let cursor = Cursor::Custom(custom_cursors[self.custom_idx].clone());
self.window.set_cursor(cursor);
}
/// Resize the window to the new size.
fn resize(&mut self, _size: PhysicalSize<u32>) {
#[cfg(not(any(android_platform, ios_platform)))]
{
let (width, height) =
match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) {
(Some(width), Some(height)) => (width, height),
_ => return,
};
self.surface
.resize(width, height)
.expect("failed to resize inner buffer");
}
self.window.request_redraw();
}
/// Change the theme.
fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
self.window.request_redraw();
}
/// Show window menu.
fn show_menu(&self) {
if let Some(position) = self.cursor_position {
self.window.show_window_menu(position);
}
}
/// Drag the window.
fn drag_window(&self) {
if let Err(err) = self.window.drag_window() {
println!("Error starting window drag: {err}");
} else {
println!("Dragging window Window={:?}", self.window.id());
}
}
/// Drag-resize the window.
fn drag_resize_window(&self) {
let position = match self.cursor_position {
Some(position) => position,
None => {
println!("Drag-resize requires cursor to be inside the window");
return;
}
};
let win_size = self.window.inner_size();
let border_size = BORDER_SIZE * self.window.scale_factor();
let x_direction = if position.x < border_size {
ResizeDirection::West
} else if position.x > (win_size.width as f64 - border_size) {
ResizeDirection::East
} else {
// Use arbitrary direction instead of None for simplicity.
ResizeDirection::SouthEast
};
let y_direction = if position.y < border_size {
ResizeDirection::North
} else if position.y > (win_size.height as f64 - border_size) {
ResizeDirection::South
} else {
// Use arbitrary direction instead of None for simplicity.
ResizeDirection::SouthEast
};
let direction = match (x_direction, y_direction) {
(ResizeDirection::West, ResizeDirection::North) => ResizeDirection::NorthWest,
(ResizeDirection::West, ResizeDirection::South) => ResizeDirection::SouthWest,
(ResizeDirection::West, _) => ResizeDirection::West,
(ResizeDirection::East, ResizeDirection::North) => ResizeDirection::NorthEast,
(ResizeDirection::East, ResizeDirection::South) => ResizeDirection::SouthEast,
(ResizeDirection::East, _) => ResizeDirection::East,
(_, ResizeDirection::South) => ResizeDirection::South,
(_, ResizeDirection::North) => ResizeDirection::North,
_ => return,
};
if let Err(err) = self.window.drag_resize_window(direction) {
println!("Error starting window drag-resize: {err}");
} else {
println!("Drag-resizing window Window={:?}", self.window.id());
}
}
/// Change window occlusion state.
fn set_occluded(&mut self, occluded: bool) {
self.occluded = occluded;
if !occluded {
self.window.request_redraw();
}
}
/// Draw the window contents.
#[cfg(not(any(android_platform, ios_platform)))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
if self.occluded {
println!("Skipping drawing occluded window={:?}", self.window.id());
return Ok(());
}
const WHITE: u32 = 0xFFFFFFFF;
const DARK_GRAY: u32 = 0xFF181818;
let color = match self.theme {
Theme::Light => WHITE,
Theme::Dark => DARK_GRAY,
};
let mut buffer = self.surface.buffer_mut()?;
buffer.fill(color);
self.window.pre_present_notify();
buffer.present()?;
Ok(())
}
#[cfg(any(android_platform, ios_platform))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
println!("Drawing but without rendering...");
Ok(())
}
}
struct Binding<T: Eq> {
trigger: T,
mods: ModifiersState,
@@ -747,6 +679,7 @@ impl<T: Eq> Binding<T> {
}
}
/// Helper enum describing the different kinds of actions this example can do.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Action {
CloseWindow,