use runmat_builtins::Value;
use super::axis_ticks::TickAxis;
use super::properties::{resolve_plot_handle, PlotHandle};
use super::state::{
axis_tick_angles_snapshot, axis_tick_angles_snapshot_for_axes, decode_axes_handle,
set_axis_tick_angles, set_axis_tick_angles_for_axes,
};
use super::{plotting_error, plotting_error_with_source};
use crate::BuiltinResult;
#[derive(Clone, Debug)]
enum TickAngleTarget {
Current,
Axes(Vec<(super::state::FigureHandle, usize)>),
}
pub fn axis_tick_angle_builtin(
builtin: &'static str,
axis: TickAxis,
args: Vec<Value>,
) -> BuiltinResult<Value> {
let (target, args) = split_optional_axes_target(builtin, args)?;
match args.as_slice() {
[] => query_angle(builtin, axis, target),
[value] => {
let angle = angle_from_value(value, builtin)?;
set_angle(builtin, axis, target, angle)?;
Ok(Value::Num(angle))
}
_ => Err(plotting_error(
builtin,
format!("{builtin}: expected zero or one tick-angle argument"),
)),
}
}
fn split_optional_axes_target(
builtin: &'static str,
args: Vec<Value>,
) -> BuiltinResult<(TickAngleTarget, Vec<Value>)> {
let mut iter = args.into_iter();
let Some(first) = iter.next() else {
return Ok((TickAngleTarget::Current, Vec::new()));
};
match resolve_plot_handle(&first, builtin) {
Ok(PlotHandle::Axes(handle, axes_index)) => {
return Ok((
TickAngleTarget::Axes(vec![(handle, axes_index)]),
iter.collect(),
));
}
Ok(
PlotHandle::Ruler(..)
| PlotHandle::Text(..)
| PlotHandle::Legend(..)
| PlotHandle::PlotChild(_, _),
) => {
return Err(plotting_error(
builtin,
format!("{builtin}: expected axes handle or tick angle"),
));
}
Ok(PlotHandle::Root | PlotHandle::Figure(_)) | Err(_) => {}
}
if let Some(targets) = axes_array_targets(&first) {
return Ok((TickAngleTarget::Axes(targets), iter.collect()));
}
let mut rest = Vec::with_capacity(iter.size_hint().0 + 1);
rest.push(first);
rest.extend(iter);
Ok((TickAngleTarget::Current, rest))
}
fn axes_array_targets(value: &Value) -> Option<Vec<(super::state::FigureHandle, usize)>> {
let Value::Tensor(tensor) = value else {
return None;
};
if tensor.data.is_empty() {
return None;
}
let mut targets = Vec::with_capacity(tensor.data.len());
for scalar in &tensor.data {
let Ok((handle, axes_index)) = decode_axes_handle(*scalar) else {
return None;
};
if !super::state::axes_handle_exists(handle, axes_index) {
return None;
}
targets.push((handle, axes_index));
}
Some(targets)
}
fn query_angle(
builtin: &'static str,
axis: TickAxis,
target: TickAngleTarget,
) -> BuiltinResult<Value> {
let (x_angle, y_angle) = match target {
TickAngleTarget::Axes(targets) if targets.len() == 1 => {
let (handle, axes_index) = targets[0];
axis_tick_angles_snapshot_for_axes(handle, axes_index).map_err(|err| {
plotting_error_with_source(builtin, format!("{builtin}: {err}"), err)
})?
}
TickAngleTarget::Axes(_) => {
return Err(plotting_error(
builtin,
format!("{builtin}: query form requires a scalar axes handle"),
))
}
TickAngleTarget::Current => axis_tick_angles_snapshot(),
};
Ok(Value::Num(match axis {
TickAxis::X => x_angle.unwrap_or(0.0),
TickAxis::Y => y_angle.unwrap_or(0.0),
}))
}
fn set_angle(
builtin: &'static str,
axis: TickAxis,
target: TickAngleTarget,
angle: f64,
) -> BuiltinResult<()> {
match target {
TickAngleTarget::Axes(targets) => {
for (handle, axes_index) in targets {
let (x_angle, y_angle) = axis_tick_angles_snapshot_for_axes(handle, axes_index)
.map_err(|err| {
plotting_error_with_source(builtin, format!("{builtin}: {err}"), err)
})?;
let (x, y) = match axis {
TickAxis::X => (Some(angle), y_angle),
TickAxis::Y => (x_angle, Some(angle)),
};
set_axis_tick_angles_for_axes(handle, axes_index, x, y).map_err(|err| {
plotting_error_with_source(builtin, format!("{builtin}: {err}"), err)
})?;
}
Ok(())
}
TickAngleTarget::Current => {
let (x_angle, y_angle) = axis_tick_angles_snapshot();
let (x, y) = match axis {
TickAxis::X => (Some(angle), y_angle),
TickAxis::Y => (x_angle, Some(angle)),
};
set_axis_tick_angles(x, y);
Ok(())
}
}
}
fn angle_from_value(value: &Value, builtin: &'static str) -> BuiltinResult<f64> {
let angle = scalar_numeric_value(value)
.ok_or_else(|| plotting_error(builtin, format!("{builtin}: tick angle must be numeric")))?;
if angle.is_finite() {
Ok(angle)
} else {
Err(plotting_error(
builtin,
format!("{builtin}: tick angle must be finite"),
))
}
}
fn scalar_numeric_value(value: &Value) -> Option<f64> {
match value {
Value::Num(value) => Some(*value),
Value::Int(value) => Some(value.to_f64()),
Value::Tensor(tensor) if tensor.data.len() == 1 => Some(tensor.data[0]),
_ => None,
}
}