Refactored view-api to use Txt.

This commit is contained in:
Samuel Guerra 2023-11-28 17:04:32 -03:00
parent 87c310c3b7
commit 2ab4d80d79
39 changed files with 262 additions and 228 deletions

View File

@ -138,3 +138,4 @@ bytemuck
accesskit
ime
Imm
ro

View File

@ -35,7 +35,6 @@ TextInput! {
# Split Core
* Txt type crate.
- Use it in the zero-ui-api crate.
- Move `NameIdMap` to unique_id crate and make it generate for every ID type?
* Font service, segmenting and shaping.

View File

@ -671,7 +671,7 @@ impl TextEditor {
self.txt_touched.set(false);
}
Err(e) => {
self.handle_error("reading file", e.to_string()).await;
self.handle_error("reading file", e.to_text()).await;
}
}
}
@ -718,7 +718,7 @@ impl TextEditor {
}
FileDialogResponse::Cancel => {}
FileDialogResponse::Error(e) => {
self.handle_error("saving file", e.to_string()).await;
self.handle_error("saving file", e.to_text()).await;
}
}
@ -741,7 +741,7 @@ impl TextEditor {
match r {
Ok(()) => true,
Err(e) => {
self.handle_error("writing file", e.to_string()).await;
self.handle_error("writing file", e.to_text()).await;
false
}
}
@ -768,14 +768,14 @@ impl TextEditor {
}
}
async fn handle_error(&self, context: &'static str, e: String) {
async fn handle_error(&self, context: &'static str, e: Txt) {
tracing::error!("error {context}, {e}");
use zero_ui::core::app::view_process::*;
let dlg = MsgDialog {
title: "Error".into(),
message: format!("Error {context}.\n\n{e}"),
message: formatx!("Error {context}.\n\n{e}"),
icon: MsgDialogIcon::Error,
buttons: MsgDialogButtons::Ok,
};

View File

@ -514,8 +514,8 @@ fn native() -> impl UiNode {
on_click = async_hn!(|_| {
use zero_ui::core::app::view_process::*;
let rsp = WINDOWS.native_message_dialog(WINDOW.id(), MsgDialog {
title: "Question?".to_owned(),
message: "Example message. Yes -> Warn, No -> Error.".to_owned(),
title: Txt::from_static("Question?"),
message: Txt::from_static("Example message. Yes -> Warn, No -> Error."),
icon: MsgDialogIcon::Info,
buttons: MsgDialogButtons::YesNo,
}).wait_rsp().await;
@ -532,8 +532,8 @@ fn native() -> impl UiNode {
},
};
WINDOWS.native_message_dialog(WINDOW.id(), MsgDialog {
title: "Title".to_owned(),
message: "Message".to_owned(),
title: Txt::from_static("Title"),
message: Txt::from_static("Message"),
icon,
buttons: MsgDialogButtons::Ok,
});
@ -594,7 +594,7 @@ fn native() -> impl UiNode {
dlg.title = "Save File".into();
dlg.kind = FileDialogKind::SaveFile;
dlg.starting_dir = first_file.parent().map(|p| p.to_owned()).unwrap_or_default();
dlg.starting_name = first_file.file_name().map(|p| p.to_string_lossy().into_owned()).unwrap_or_default();
dlg.starting_name = first_file.file_name().map(|p| Txt::from_str(&p.to_string_lossy())).unwrap_or_default();
let res = WINDOWS.native_file_dialog(WINDOW.id(), dlg.clone()).wait_rsp().await;
let save_file = match res {
FileDialogResponse::Selected(mut s) => {

View File

@ -6,6 +6,7 @@ use std::{
sync::{self, Arc},
};
use zero_ui_txt::Txt;
pub use zero_ui_view_api::{
self,
api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionNameError, ApiExtensionPayload, ApiExtensionRecvError, ApiExtensions},
@ -213,14 +214,14 @@ impl VIEW_PROCESS {
/// Returns a list of image decoders supported by the view-process backend.
///
/// Each string is the lower-case file extension.
pub fn image_decoders(&self) -> Result<Vec<String>> {
pub fn image_decoders(&self) -> Result<Vec<Txt>> {
self.write().process.image_decoders()
}
/// Returns a list of image encoders supported by the view-process backend.
///
/// Each string is the lower-case file extension.
pub fn image_encoders(&self) -> Result<Vec<String>> {
pub fn image_encoders(&self) -> Result<Vec<Txt>> {
self.write().process.image_encoders()
}
@ -424,7 +425,7 @@ impl VIEW_PROCESS {
}
}
pub(super) fn on_image_error(&self, id: ImageId, error: String) -> Option<ViewImage> {
pub(super) fn on_image_error(&self, id: ImageId, error: Txt) -> Option<ViewImage> {
if let Some(i) = self.loading_image_index(id) {
let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
{
@ -470,13 +471,13 @@ impl VIEW_PROCESS {
i.map(|i| ViewImage(app.frame_images.swap_remove(i).upgrade().unwrap()))
}
pub(super) fn on_image_encoded(&self, id: ImageId, format: String, data: IpcBytes) {
pub(super) fn on_image_encoded(&self, id: ImageId, format: Txt, data: IpcBytes) {
self.on_image_encode_result(id, format, Ok(data));
}
pub(super) fn on_image_encode_error(&self, id: ImageId, format: String, error: String) {
pub(super) fn on_image_encode_error(&self, id: ImageId, format: Txt, error: Txt) {
self.on_image_encode_result(id, format, Err(EncodeError::Encode(error)));
}
fn on_image_encode_result(&self, id: ImageId, format: String, result: std::result::Result<IpcBytes, EncodeError>) {
fn on_image_encode_result(&self, id: ImageId, format: Txt, result: std::result::Result<IpcBytes, EncodeError>) {
let mut app = self.write();
app.encoding_images.retain(move |r| {
let done = r.image_id == id && r.format == format;
@ -509,7 +510,7 @@ impl VIEW_PROCESS {
let mut app = self.write();
app.pending_frames = 0;
for (_, r) in app.message_dialogs.drain(..) {
r.respond(MsgDialogResponse::Error("respawn".to_owned()));
r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
}
}
@ -648,7 +649,7 @@ impl ViewWindow {
}
/// Set the window title.
pub fn set_title(&self, title: String) -> Result<()> {
pub fn set_title(&self, title: Txt) -> Result<()> {
self.0.call(|id, p| p.set_title(id, title))
}
@ -1100,7 +1101,7 @@ struct ViewImageData {
is_opaque: bool,
partial_pixels: Option<IpcBytes>,
pixels: Option<std::result::Result<IpcBytes, String>>,
pixels: Option<std::result::Result<IpcBytes, Txt>>,
is_mask: bool,
done_signal: SignalOnce,
@ -1151,7 +1152,7 @@ impl ViewImage {
}
/// Returns the load error if one happened.
pub fn error(&self) -> Option<String> {
pub fn error(&self) -> Option<Txt> {
self.0.read().pixels.as_ref().and_then(|s| s.as_ref().err().cloned())
}
@ -1225,7 +1226,7 @@ impl ViewImage {
}
/// Create a dummy image in the loaded or error state.
pub fn dummy(error: Option<String>) -> Self {
pub fn dummy(error: Option<Txt>) -> Self {
ViewImage(Arc::new(RwLock::new(ViewImageData {
app_id: None,
id: None,
@ -1255,7 +1256,7 @@ impl ViewImage {
/// The `format` must be one of the [`image_encoders`] supported by the view-process backend.
///
/// [`image_encoders`]: View::image_encoders.
pub async fn encode(&self, format: String) -> std::result::Result<IpcBytes, EncodeError> {
pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
self.awaiter().await;
if let Some(e) = self.error() {
@ -1297,7 +1298,7 @@ impl ViewImage {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncodeError {
/// Encode error.
Encode(String),
Encode(Txt),
/// Attempted to encode dummy image.
///
/// In a headless-app without renderer all images are dummy because there is no
@ -1306,8 +1307,8 @@ pub enum EncodeError {
/// The View-Process disconnected or has not finished initializing yet, try again after [`VIEW_PROCESS_INITED_EVENT`].
ViewProcessOffline,
}
impl From<String> for EncodeError {
fn from(e: String) -> Self {
impl From<Txt> for EncodeError {
fn from(e: Txt) -> Self {
EncodeError::Encode(e)
}
}
@ -1350,7 +1351,7 @@ impl WeakViewImage {
struct EncodeRequest {
image_id: ImageId,
format: String,
format: Txt,
listeners: Vec<flume::Sender<std::result::Result<IpcBytes, EncodeError>>>,
}
@ -1360,16 +1361,16 @@ type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
pub struct ViewClipboard {}
impl ViewClipboard {
/// Read [`ClipboardType::Text`].
pub fn read_text(&self) -> Result<ClipboardResult<String>> {
pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::Text)? {
Ok(ClipboardData::Text(t)) => Ok(Ok(t)),
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other("view-process returned incorrect type".to_owned()))),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
/// Write [`ClipboardType::Text`].
pub fn write_text(&self, txt: String) -> Result<ClipboardResult<()>> {
pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Text(txt))
}
@ -1379,7 +1380,7 @@ impl ViewClipboard {
match app.process.read_clipboard(ClipboardType::Image)? {
Ok(ClipboardData::Image(id)) => {
if id == ImageId::INVALID {
Ok(Err(ClipboardError::Other("view-process returned invalid image".to_owned())))
Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
} else {
let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
id: Some(id),
@ -1399,7 +1400,7 @@ impl ViewClipboard {
}
}
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other("view-process returned incorrect type".to_owned()))),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
@ -1410,7 +1411,7 @@ impl ViewClipboard {
return VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Image(id));
}
}
Ok(Err(ClipboardError::Other("image not loaded".to_owned())))
Ok(Err(ClipboardError::Other(Txt::from_static("image not loaded"))))
}
/// Read [`ClipboardType::FileList`].
@ -1418,7 +1419,7 @@ impl ViewClipboard {
match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::FileList)? {
Ok(ClipboardData::FileList(f)) => Ok(Ok(f)),
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other("view-process returned incorrect type".to_owned()))),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
@ -1428,7 +1429,7 @@ impl ViewClipboard {
}
/// Read [`ClipboardType::Extension`].
pub fn read_extension(&self, data_type: String) -> Result<ClipboardResult<IpcBytes>> {
pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
match VIEW_PROCESS
.try_write()?
.process
@ -1436,12 +1437,12 @@ impl ViewClipboard {
{
Ok(ClipboardData::Extension { data_type: rt, data }) if rt == data_type => Ok(Ok(data)),
Err(e) => Ok(Err(e)),
_ => Ok(Err(ClipboardError::Other("view-process returned incorrect type".to_owned()))),
_ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
}
}
/// Write [`ClipboardType::Extension`].
pub fn write_extension(&self, data_type: String, data: IpcBytes) -> Result<ClipboardResult<()>> {
pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
VIEW_PROCESS
.try_write()?
.process

View File

@ -54,7 +54,7 @@ pub enum ClipboardError {
/// Other error.
///
/// The string can be a debug description of the error, only suitable for logging.
Other(String),
Other(Txt),
}
/// Clipboard service.
@ -99,7 +99,9 @@ impl CLIPBOARD {
Ok(r) => match r {
Ok(()) => Ok(()),
Err(e) => match e {
clipboard_api::ClipboardError::NotFound => Err(ClipboardError::Other("not found error in set operation".to_owned())),
clipboard_api::ClipboardError::NotFound => {
Err(ClipboardError::Other(Txt::from_static("not found error in set operation")))
}
clipboard_api::ClipboardError::NotSupported => Err(ClipboardError::NotSupported),
clipboard_api::ClipboardError::Other(e) => Err(ClipboardError::Other(e)),
},
@ -118,7 +120,7 @@ impl CLIPBOARD {
}
/// Sets the text string on the clipboard, returns `Ok(())` if the operation succeeded.
pub fn set_text(&self, txt: impl Into<Txt>) -> Result<(), ClipboardError> {
self.set(|v| v.write_text(txt.into().into()))
self.set(|v| v.write_text(txt.into()))
}
/// Gets an image from the clipboard.
@ -161,14 +163,14 @@ impl CLIPBOARD {
/// Gets custom data from the clipboard.
///
/// The current view-process must support `data_type`.
pub fn extension(&self, data_type: impl Into<String>) -> Result<Option<IpcBytes>, ClipboardError> {
pub fn extension(&self, data_type: impl Into<Txt>) -> Result<Option<IpcBytes>, ClipboardError> {
self.get(|v| v.read_extension(data_type.into()))
}
/// Set a custom data on the clipboard.
///
/// The current view-process must support `data_type`.
pub fn set_extension(&self, data_type: impl Into<String>, data: IpcBytes) -> Result<(), ClipboardError> {
pub fn set_extension(&self, data_type: impl Into<Txt>, data: IpcBytes) -> Result<(), ClipboardError> {
self.set(|v| v.write_extension(data_type.into(), data))
}
}

View File

@ -13,13 +13,13 @@ pub use trace::*;
mod local;
pub use local::*;
use zero_ui_txt::formatx;
use crate::{
app::{AppDisconnected, AppEventSender, LoopTimer},
context_var,
crate_util::{Handle, HandleOwner, IdSet, WeakHandle},
event::{Event, EventArgs, EventHandle, EventHandles, EventUpdate, EVENTS, EVENTS_SV},
formatx,
handler::{AppHandler, AppHandlerArgs, AppWeakHandle},
render::ReuseRange,
text::Txt,
@ -768,7 +768,7 @@ impl WIDGET {
} else if let Some(id) = self.try_id() {
formatx!("<no-window>//{id:?}")
} else {
Txt::from("<no-widget>")
Txt::from_str("<no-widget>")
}
}

View File

@ -1026,7 +1026,7 @@ impl CommandNameExt for Command {
if shortcut.is_empty() {
name.clone()
} else {
crate::formatx!("{name} ({})", shortcut[0])
zero_ui_txt::formatx!("{name} ({})", shortcut[0])
}
})
.boxed()

View File

@ -12,6 +12,7 @@ use std::{
};
use parking_lot::Mutex;
use zero_ui_txt::{formatx, ToText};
use zero_ui_view_api::ipc::IpcBytes;
use crate::{
@ -464,7 +465,7 @@ impl ImagesService {
ImageSource::Read(path) => {
let path = crate::crate_util::absolute_path(&path, || env::current_dir().expect("could not access current dir"), true);
if !limits.allow_path.allows(&path) {
let error = format!("limits filter blocked `{}`", path.display());
let error = formatx!("limits filter blocked `{}`", path.display());
tracing::error!("{error}");
return var(Img::dummy(Some(error))).read_only();
}
@ -473,7 +474,7 @@ impl ImagesService {
#[cfg(http)]
ImageSource::Download(uri, accepts) => {
if !limits.allow_uri.allows(&uri) {
let error = format!("limits filter blocked `{uri}`");
let error = formatx!("limits filter blocked `{uri}`");
tracing::error!("{error}");
return var(Img::dummy(Some(error))).read_only();
}
@ -552,15 +553,15 @@ impl ImagesService {
format: path
.extension()
.and_then(|e| e.to_str())
.map(|s| ImageDataFormat::FileExtension(s.to_owned()))
.map(|s| ImageDataFormat::FileExtension(Txt::from_str(s)))
.unwrap_or(ImageDataFormat::Unknown),
r: Err(String::new()),
r: Err(Txt::from_static("")),
};
let mut file = match fs::File::open(path).await {
Ok(f) => f,
Err(e) => {
r.r = Err(e.to_string());
r.r = Err(e.to_text());
return r;
}
};
@ -568,20 +569,20 @@ impl ImagesService {
let len = match file.metadata().await {
Ok(m) => m.len() as usize,
Err(e) => {
r.r = Err(e.to_string());
r.r = Err(e.to_text());
return r;
}
};
if len > max_encoded_size.0 {
r.r = Err(format!("file size `{}` exceeds the limit of `{max_encoded_size}`", len.bytes()));
r.r = Err(formatx!("file size `{}` exceeds the limit of `{max_encoded_size}`", len.bytes()));
return r;
}
let mut data = Vec::with_capacity(len);
r.r = match file.read_to_end(&mut data).await {
Ok(_) => Ok(IpcBytes::from_vec(data)),
Err(e) => Err(e.to_string()),
Err(e) => Err(e.to_text()),
};
r
@ -600,7 +601,7 @@ impl ImagesService {
task::run(async move {
let mut r = ImageData {
format: ImageDataFormat::Unknown,
r: Err(String::new()),
r: Err(Txt::from_static("")),
};
let request = task::http::Request::get(uri)
@ -615,21 +616,21 @@ impl ImagesService {
if let Some(m) = rsp.headers().get(&task::http::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()) {
let m = m.to_lowercase();
if m.starts_with("image/") {
r.format = ImageDataFormat::MimeType(m);
r.format = ImageDataFormat::MimeType(Txt::from_str(&m));
}
}
match rsp.bytes().await {
Ok(d) => r.r = Ok(IpcBytes::from_vec(d)),
Err(e) => {
r.r = Err(format!("download error: {e}"));
r.r = Err(formatx!("download error: {e}"));
}
}
let _ = rsp.consume().await;
}
Err(e) => {
r.r = Err(format!("request error: {e}"));
r.r = Err(formatx!("request error: {e}"));
}
}
@ -788,7 +789,7 @@ impl IMAGES {
}
/// Returns a dummy image that reports it is loaded or an error.
pub fn dummy(&self, error: Option<String>) -> ImageVar {
pub fn dummy(&self, error: Option<Txt>) -> ImageVar {
var(Img::dummy(error)).read_only()
}
@ -805,7 +806,7 @@ impl IMAGES {
pub fn download(&self, uri: impl task::http::TryUri, accept: Option<Txt>) -> ImageVar {
match uri.try_uri() {
Ok(uri) => self.cache(ImageSource::Download(uri, accept)),
Err(e) => self.dummy(Some(e.to_string())),
Err(e) => self.dummy(Some(e.to_text())),
}
}
@ -951,5 +952,5 @@ impl IMAGES {
}
struct ImageData {
format: ImageDataFormat,
r: std::result::Result<IpcBytes, String>,
r: std::result::Result<IpcBytes, Txt>,
}

View File

@ -124,7 +124,7 @@ impl Img {
}
/// Create a dummy image in the loaded or error state.
pub fn dummy(error: Option<String>) -> Self {
pub fn dummy(error: Option<Txt>) -> Self {
Self::new(ViewImage::dummy(error))
}
@ -266,10 +266,10 @@ impl Img {
}
/// Encode the image to the format.
pub async fn encode(&self, format: String) -> std::result::Result<zero_ui_view_api::ipc::IpcBytes, EncodeError> {
pub async fn encode(&self, format: Txt) -> std::result::Result<zero_ui_view_api::ipc::IpcBytes, EncodeError> {
self.done_signal.clone().await;
if let Some(e) = self.error() {
Err(EncodeError::Encode(e.into()))
Err(EncodeError::Encode(e))
} else {
self.view.get().unwrap().encode(format).await
}
@ -281,7 +281,7 @@ impl Img {
pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
let path = path.into();
if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
self.save_impl(ext.to_owned(), path).await
self.save_impl(Txt::from_str(ext), path).await
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
@ -293,11 +293,11 @@ impl Img {
/// Encode and write the image to `path`.
///
/// The image is encoded to the `format`, the file extension can be anything.
pub async fn save_with_format(&self, format: String, path: impl Into<PathBuf>) -> io::Result<()> {
pub async fn save_with_format(&self, format: Txt, path: impl Into<PathBuf>) -> io::Result<()> {
self.save_impl(format, path.into()).await
}
async fn save_impl(&self, format: String, path: PathBuf) -> io::Result<()> {
async fn save_impl(&self, format: Txt, path: PathBuf) -> io::Result<()> {
let view = self.view.get().unwrap();
let data = view
.encode(format)

View File

@ -1207,27 +1207,6 @@ pub enum UnderlinePosition {
Descent,
}
///<span data-del-macro-root></span> Creates a [`Txt`] by formatting using the [`format_args!`] syntax.
///
/// Note that this behaves like a [`format!`] for [`Txt`], but it can be more performant because the
/// text type can represent `&'static str` and can i
///
/// # Examples
///
/// ```
/// # use zero_ui_core::text::formatx;
/// let text = formatx!("Hello {}", "World!");
/// ```
///
/// [`Txt`]: crate::text::Txt
#[macro_export]
macro_rules! formatx {
($($tt:tt)*) => {
$crate::text::Txt::from_fmt(format_args!($($tt)*))
};
}
#[doc(inline)]
pub use crate::formatx;
use crate::var::{IntoVar, LocalVar};
#[cfg(test)]

View File

@ -35,6 +35,7 @@ pub use iter::TreeFilter;
mod hit;
pub(crate) use hit::HitTestClips;
use zero_ui_txt::formatx;
pub use self::hit::RelativeHitZ;
use self::{access::AccessEnabled, iter::TreeIterator};
@ -1056,13 +1057,13 @@ impl WidgetInfo {
let id = self.id();
let name = id.name();
if !name.is_empty() {
return crate::formatx!("{mod_ident}!({name:?})");
return formatx!("{mod_ident}!({name:?})");
} else {
return crate::formatx!("{mod_ident}!({})", id.sequential());
return formatx!("{mod_ident}!({})", id.sequential());
}
}
}
crate::formatx!("{}", self.id())
formatx!("{}", self.id())
}
/// Full path to this widget with [`interactivity`] values.

View File

@ -1221,9 +1221,9 @@ impl PartialEq for AccessStateSource {
impl From<&AccessStateSource> for AccessState {
fn from(value: &AccessStateSource) -> Self {
match value {
AccessStateSource::Label(l) => AccessState::Label(l.to_string()),
AccessStateSource::Placeholder(p) => AccessState::Placeholder(p.to_string()),
AccessStateSource::ValueText(v) => AccessState::ValueText(v.to_string()),
AccessStateSource::Label(l) => AccessState::Label(l.clone()),
AccessStateSource::Placeholder(p) => AccessState::Placeholder(p.clone()),
AccessStateSource::ValueText(v) => AccessState::ValueText(v.clone()),
AccessStateSource::ScrollHorizontal(x) => AccessState::ScrollHorizontal(x.get().0),
AccessStateSource::ScrollVertical(y) => AccessState::ScrollVertical(y.get().0),
}

View File

@ -413,7 +413,7 @@ impl HeadedCtrl {
if let Some(title) = self.vars.title().get_new() {
self.update_gen(move |view| {
let _: Ignore = view.set_title(title.into_owned());
let _: Ignore = view.set_title(title);
});
}
@ -1026,7 +1026,7 @@ impl HeadedCtrl {
let request = WindowRequest {
id: crate::app::view_process::ApiWindowId::from_raw(WINDOW.id().get()),
title: self.vars.title().get().to_string(),
title: self.vars.title().get(),
state: state.clone(),
kiosk: self.kiosk.is_some(),
default_position: system_pos,
@ -1154,7 +1154,7 @@ impl HeadedCtrl {
let request = WindowRequest {
id: crate::app::view_process::ApiWindowId::from_raw(WINDOW.id().get()),
title: self.vars.title().get_string(),
title: self.vars.title().get(),
state: self.state.clone().unwrap(),
kiosk: self.kiosk.is_some(),
default_position: false,

View File

@ -4,6 +4,7 @@ use std::{fmt, mem};
use parking_lot::Mutex;
use rayon::prelude::*;
use zero_ui_txt::{formatx, Txt};
use super::commands::WindowCommands;
use super::*;
@ -154,13 +155,13 @@ impl WindowsService {
self.frame_images.push(img.clone());
img.read_only()
}
Err(_) => var(Img::dummy(Some(format!("{}", WindowNotFound(window_id))))).read_only(),
Err(_) => var(Img::dummy(Some(formatx!("{}", WindowNotFound(window_id))))).read_only(),
}
} else {
var(Img::dummy(Some(format!("window `{window_id}` is headless without renderer")))).read_only()
var(Img::dummy(Some(formatx!("window `{window_id}` is headless without renderer")))).read_only()
}
} else {
var(Img::dummy(Some(format!("{}", WindowNotFound(window_id))))).read_only()
var(Img::dummy(Some(formatx!("{}", WindowNotFound(window_id))))).read_only()
}
}
@ -1127,10 +1128,10 @@ impl WINDOWS {
WINDOWS_SV.write().view_window_task(window_id, move |win| match win {
Some(win) => {
if let Err(e) = win.message_dialog(dialog, responder.clone()) {
responder.respond(view_process::MsgDialogResponse::Error(format!("{e}")))
responder.respond(view_process::MsgDialogResponse::Error(formatx!("{e}")))
}
}
None => responder.respond(view_process::MsgDialogResponse::Error("native window not found".to_owned())),
None => responder.respond(view_process::MsgDialogResponse::Error(Txt::from_static("native window not found"))),
});
rsp
}
@ -1148,10 +1149,10 @@ impl WINDOWS {
WINDOWS_SV.write().view_window_task(window_id, move |win| match win {
Some(win) => {
if let Err(e) = win.file_dialog(dialog, responder.clone()) {
responder.respond(view_process::FileDialogResponse::Error(format!("{e}")))
responder.respond(view_process::FileDialogResponse::Error(formatx!("{e}")))
}
}
None => responder.respond(view_process::FileDialogResponse::Error("native window not found".to_owned())),
None => responder.respond(view_process::FileDialogResponse::Error(Txt::from_static("native window not found"))),
});
rsp
}

View File

@ -626,3 +626,21 @@ impl<T: ToString> ToText for T {
self.to_string().into()
}
}
///<span data-del-macro-root></span> Creates a [`Txt`] by formatting using the [`format_args!`] syntax.
///
/// Note that this behaves like a [`format!`] for [`Txt`], but it can be more performant because the
/// text type can represent `&'static str` and can i
///
/// # Examples
///
/// ```
/// # use zero_ui_core::text::formatx;
/// let text = formatx!("Hello {}", "World!");
/// ```
#[macro_export]
macro_rules! formatx {
($($tt:tt)*) => {
$crate::Txt::from_fmt(format_args!($($tt)*))
};
}

View File

@ -19,6 +19,7 @@ webrender_api = { git = "https://github.com/servo/webrender.git", rev = "8589629
euclid = { version = "0.22.6", features = ["serde", "bytemuck"] } # same version as webrender, but with bytemuck
zero-ui-units = { path = "../zero-ui-units" }
zero-ui-txt = { path = "../zero-ui-txt" }
serde = { version = "1.0", features = ["derive"] }
serde_bytes = "0.11"

View File

@ -5,6 +5,7 @@ use std::{num::NonZeroU32, ops};
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use zero_ui_txt::Txt;
use zero_ui_units::{PxRect, PxSize, PxTransform};
/// Accessibility role of a node in the accessibility tree.
@ -239,7 +240,7 @@ pub enum AccessState {
Invalid(Invalid),
/// Defines a string value that labels the widget.
Label(String),
Label(Txt),
/// Defines the hierarchical level of an widget within a structure.
Level(NonZeroU32),
@ -250,7 +251,7 @@ pub enum AccessState {
/// Indicates whether the widget's orientation is horizontal, vertical, or unknown/ambiguous.
Orientation(Orientation),
/// Short hint (a word or short phrase) intended to help the user with data entry when a form control has no value.
Placeholder(String),
Placeholder(Txt),
/// Indicates that the widget is not editable, but is otherwise operable.
ReadOnly,
/// Indicates that user input is required on the widget before a form may be submitted.
@ -268,7 +269,7 @@ pub enum AccessState {
/// Defines a human readable version of the [`Value`].
///
/// [`Value`]: Self::Value
ValueText(String),
ValueText(Txt),
/// Indicate that the widget can change.
Live {
@ -479,7 +480,7 @@ pub enum AccessCmd {
Scroll(ScrollCmd),
/// Insert the text.
ReplaceSelectedText(String),
ReplaceSelectedText(Txt),
/// Set the text selection.
///
@ -495,7 +496,7 @@ pub enum AccessCmd {
/// Replace the value of the control with the specified value and
/// reset the selection, if applicable.
SetString(String),
SetString(Txt),
/// Replace the value of the control with the specified value and
/// reset the selection, if applicable.

View File

@ -1 +0,0 @@
//! Axis analog device types.

View File

@ -3,6 +3,7 @@
use std::{fmt, ops};
use serde::{Deserialize, Serialize};
use zero_ui_txt::Txt;
/// Custom serialized data, in a format defined by the extension.
///
@ -25,7 +26,7 @@ impl ApiExtensionPayload {
if let Some((id, error)) = self.parse_invalid_request() {
Err(ApiExtensionRecvError::InvalidRequest {
extension_id: id,
error: error.to_owned(),
error: Txt::from_str(error),
})
} else if let Some(id) = self.parse_unknown_extension() {
Err(ApiExtensionRecvError::UnknownExtension { extension_id: id })
@ -116,17 +117,17 @@ impl fmt::Debug for ApiExtensionPayload {
/// by using the extension payload
#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct ApiExtensionName {
name: String,
name: Txt,
}
impl ApiExtensionName {
/// New from unique name.
///
/// The name must contain at least 1 characters, and match the pattern `[a-zA-Z][a-zA-Z0-9-_.]`.
pub fn new(name: impl Into<String>) -> Result<Self, ApiExtensionNameError> {
pub fn new(name: impl Into<Txt>) -> Result<Self, ApiExtensionNameError> {
let name = name.into();
Self::new_impl(name)
}
fn new_impl(name: String) -> Result<ApiExtensionName, ApiExtensionNameError> {
fn new_impl(name: Txt) -> Result<ApiExtensionName, ApiExtensionNameError> {
if name.is_empty() {
return Err(ApiExtensionNameError::NameCannotBeEmpty);
}
@ -316,7 +317,7 @@ pub enum ApiExtensionRecvError {
/// Is `INVALID` only if error message is corrupted.
extension_id: ApiExtensionId,
/// Message from the view-process.
error: String,
error: Txt,
},
/// Failed to deserialize to the expected response type.
Deserialize(bincode::Error),

View File

@ -8,6 +8,8 @@ use std::{
#[cfg(feature = "ipc")]
use std::time::Duration;
use zero_ui_txt::Txt;
use crate::{ipc, AnyResult, Event, Request, Response, ViewConfig, ViewProcessGen, ViewProcessOffline, VpResult};
/// The listener returns the closure on join for reuse in respawn.
@ -208,8 +210,8 @@ impl Controller {
// create process and spawn it, unless is running in same process mode.
let process = if ViewConfig::is_awaiting_same_process() {
ViewConfig::set_same_process(ViewConfig {
version: crate::VERSION.to_owned(),
server_name: init.name().to_owned(),
version: crate::VERSION.into(),
server_name: Txt::from_str(init.name()),
headless,
});
None

View File

@ -2,6 +2,8 @@
use std::{fmt, path::PathBuf};
use zero_ui_txt::Txt;
use crate::{image::ImageId, ipc::IpcBytes};
/// Clipboard data.
@ -10,7 +12,7 @@ pub enum ClipboardData {
/// Text string.
///
/// View-process can convert between [`String`] and the text formats of the platform.
Text(String),
Text(Txt),
/// Image data.
///
/// View-process reads from clipboard in any format supported and starts an image decode task
@ -23,7 +25,7 @@ pub enum ClipboardData {
/// Any data format supported only by the specific view-process implementation.
Extension {
/// Type key, must be in a format defined by the view-process.
data_type: String,
data_type: Txt,
/// The raw data.
data: IpcBytes,
},
@ -39,7 +41,7 @@ pub enum ClipboardType {
/// A [`ClipboardData::FileList`].
FileList,
/// A [`ClipboardData::Extension`].
Extension(String),
Extension(Txt),
}
/// Clipboard read/write error.
@ -52,7 +54,7 @@ pub enum ClipboardError {
/// Other error.
///
/// The string can be a debug description of the error, only suitable for logging.
Other(String),
Other(Txt),
}
impl fmt::Display for ClipboardError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -4,6 +4,7 @@ use std::{fmt, time::Duration};
use serde::{Deserialize, Serialize};
use zero_ui_txt::Txt;
use zero_ui_units::{Dip, DipSize};
/// System settings needed for implementing double/triple clicks.
@ -116,7 +117,7 @@ impl Default for AnimationsConfig {
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Deserialize, Default)]
pub struct LocaleConfig {
/// BCP-47 language tags, if the locale can be obtained.
pub langs: Vec<String>,
pub langs: Vec<Txt>,
}
/// Text anti-aliasing.

View File

@ -2,6 +2,8 @@
use std::path::PathBuf;
use zero_ui_txt::Txt;
crate::declare_id! {
/// Identifies an ongoing async native dialog with the user.
pub struct DialogId(_);
@ -11,9 +13,9 @@ crate::declare_id! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct MsgDialog {
/// Message dialog window title.
pub title: String,
pub title: Txt,
/// Message text.
pub message: String,
pub message: Txt,
/// Kind of message.
pub icon: MsgDialogIcon,
/// Message buttons.
@ -22,8 +24,8 @@ pub struct MsgDialog {
impl Default for MsgDialog {
fn default() -> Self {
Self {
title: String::new(),
message: String::new(),
title: Txt::from_str(""),
message: Txt::from_str(""),
icon: MsgDialogIcon::Info,
buttons: MsgDialogButtons::Ok,
}
@ -75,18 +77,18 @@ pub enum MsgDialogResponse {
///
/// The associated string may contain debug information, caller should assume that native file dialogs
/// are not available for the given window ID at the current view-process instance.
Error(String),
Error(Txt),
}
/// Defines a native file dialog.
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FileDialog {
/// Dialog window title.
pub title: String,
pub title: Txt,
/// Selected directory when the dialog opens.
pub starting_dir: PathBuf,
/// Starting file name.
pub starting_name: String,
pub starting_name: Txt,
/// File extension filters.
///
/// Syntax:
@ -99,7 +101,7 @@ pub struct FileDialog {
/// not glob patterns, they must be an extension (without the dot prefix) or `*` for all files.
///
/// [`push_filter`]: Self::push_filter
pub filters: String,
pub filters: Txt,
/// Defines the file dialog looks and what kind of result is expected.
pub kind: FileDialogKind,
@ -213,10 +215,10 @@ impl FileDialog {
impl Default for FileDialog {
fn default() -> Self {
FileDialog {
title: String::new(),
title: Txt::from_str(""),
starting_dir: PathBuf::new(),
starting_name: String::new(),
filters: String::new(),
starting_name: Txt::from_str(""),
filters: Txt::from_str(""),
kind: FileDialogKind::OpenFile,
}
}
@ -250,7 +252,7 @@ pub enum FileDialogResponse {
///
/// The associated string may contain debug information, caller should assume that native file dialogs
/// are not available for the given window ID at the current view-process instance.
Error(String),
Error(Txt),
}
#[cfg(test)]
@ -260,10 +262,10 @@ mod tests {
#[test]
fn file_filters() {
let mut dlg = FileDialog {
title: "".to_owned(),
title: "".into(),
starting_dir: "".into(),
starting_name: "".to_owned(),
filters: "".to_owned(),
starting_name: "".into(),
filters: "".into(),
kind: FileDialogKind::OpenFile,
};

View File

@ -3,6 +3,7 @@
use std::fmt;
use serde::{Deserialize, Serialize};
use zero_ui_txt::Txt;
use crate::ipc::IpcBytes;
use zero_ui_units::{Px, PxSize};
@ -112,17 +113,17 @@ pub enum ImageDataFormat {
/// The image is encoded, a file extension that maybe identifies
/// the format is known.
FileExtension(String),
FileExtension(Txt),
/// The image is encoded, MIME type that maybe identifies the format is known.
MimeType(String),
MimeType(Txt),
/// The image is encoded, a decoder will be selected using the "magic number"
/// on the beginning of the bytes buffer.
Unknown,
}
impl From<String> for ImageDataFormat {
fn from(ext_or_mime: String) -> Self {
impl From<Txt> for ImageDataFormat {
fn from(ext_or_mime: Txt) -> Self {
if ext_or_mime.contains('/') {
ImageDataFormat::MimeType(ext_or_mime)
} else {
@ -132,7 +133,7 @@ impl From<String> for ImageDataFormat {
}
impl From<&str> for ImageDataFormat {
fn from(ext_or_mime: &str) -> Self {
ext_or_mime.to_owned().into()
Txt::from_str(ext_or_mime).into()
}
}
impl From<PxSize> for ImageDataFormat {

View File

@ -12,6 +12,7 @@ use flume::unbounded as channel;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use zero_ui_txt::Txt;
pub(crate) type IpcResult<T> = std::result::Result<T, Disconnected>;
@ -205,13 +206,16 @@ impl std::error::Error for Disconnected {}
#[cfg(feature = "ipc")]
pub(crate) struct AppInit {
server: IpcOneShotServer<AppInitMsg>,
name: String,
name: Txt,
}
#[cfg(feature = "ipc")]
impl AppInit {
pub fn new() -> Self {
let (server, name) = IpcOneShotServer::new().expect("failed to create init channel");
AppInit { server, name }
AppInit {
server,
name: Txt::from_str(&name),
}
}
/// Unique name for the view-process to find this channel.
@ -246,10 +250,10 @@ impl AppInit {
/// Start the view-process server and waits for `(request, response, event)`.
#[cfg(feature = "ipc")]
pub fn connect_view_process(server_name: String) -> IpcResult<ViewChannels> {
pub fn connect_view_process(server_name: Txt) -> IpcResult<ViewChannels> {
let _s = tracing::trace_span!("connect_view_process").entered();
let app_init_sender = IpcSender::connect(server_name).expect("failed to connect to init channel");
let app_init_sender = IpcSender::connect(server_name.into_owned()).expect("failed to connect to init channel");
let (req_sender, req_recv) = channel().map_err(handle_io_error)?;
// Large messages can only be received in a receiver created in the same process that is receiving (on Windows)
@ -283,7 +287,7 @@ pub(crate) struct AppInit {
// )
#[allow(clippy::type_complexity)]
init: flume::Receiver<AppInitMsg>,
name: String,
name: Txt,
}
#[cfg(not(feature = "ipc"))]
mod name_map {
@ -295,7 +299,7 @@ mod name_map {
use super::AppInitMsg;
type Map = Mutex<HashMap<String, flume::Sender<AppInitMsg>>>;
type Map = Mutex<HashMap<Txt, flume::Sender<AppInitMsg>>>;
pub fn get() -> &'static Map {
static mut MAP: MaybeUninit<Map> = MaybeUninit::uninit();
@ -349,7 +353,7 @@ impl AppInit {
/// Start the view-process server and waits for `(request, response, event)`.
#[cfg(not(feature = "ipc"))]
pub fn connect_view_process(server_name: String) -> IpcResult<ViewChannels> {
pub fn connect_view_process(server_name: Txt) -> IpcResult<ViewChannels> {
let app_init_sender = name_map::get().lock().unwrap().remove(&server_name).unwrap();
let (req_sender, req_recv) = channel();

View File

@ -1,8 +1,9 @@
//!: Keyboard types.
use std::{fmt, mem, sync::Arc};
use std::{fmt, mem};
use serde::{Deserialize, Serialize};
use zero_ui_txt::Txt;
/// Contains the platform-native physical key identifier
///
@ -767,7 +768,7 @@ pub enum Key {
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Str(Arc<str>),
Str(Txt),
/// This variant is used when the key cannot be translated to any other variant.
///
@ -780,6 +781,7 @@ pub enum Key {
/// - **Web:** Always contains `None`
Dead(Option<char>),
/* SAFETY, no associated data after Alt, see `Key::all_named` */
/// The `Alt` (Alternative) key.
///
/// This key enables the alternate modifier function for interpreting concurrent or subsequent
@ -1948,9 +1950,9 @@ impl Key {
pub fn all_named() -> impl ExactSizeIterator<Item = Key> + DoubleEndedIterator {
unsafe {
// SAFETY: this is safe because all variants from `Alt` are without associated data.
let s: (u16, [u8; 22]) = mem::transmute(Key::Alt);
let e: (u16, [u8; 22]) = mem::transmute(Key::F35);
(s.0..=e.0).map(|n| mem::transmute((n, [0u8; 22])))
let s: (u16, [u8; 38]) = mem::transmute(Key::Alt);
let e: (u16, [u8; 38]) = mem::transmute(Key::F35);
(s.0..=e.0).map(|n| mem::transmute((n, [0u8; 38])))
}
}
}
@ -1969,7 +1971,7 @@ impl fmt::Debug for Key {
let name = self.name();
match self {
Self::Char(c) => write!(f, "{name}({c:?})"),
Self::Str(s) => write!(f, "{name}({:?})", s.as_ref()),
Self::Str(s) => write!(f, "{name}({:?})", s.as_str()),
Self::Dead(c) => write!(f, "{name}({c:?})"),
_ => write!(f, "{name}"),
}

View File

@ -24,7 +24,6 @@ use serde::{Deserialize, Serialize};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod access;
pub mod analog;
pub mod api_extension;
pub mod clipboard;
pub mod config;
@ -46,6 +45,7 @@ pub use app_process::*;
mod view_process;
pub use view_process::*;
use zero_ui_txt::Txt;
use std::fmt;
use webrender_api::{FontInstanceKey, FontKey, ImageKey};
@ -288,7 +288,7 @@ declare_api! {
pub fn close_window(&mut self, id: WindowId);
/// Set window title.
pub fn set_title(&mut self, id: WindowId, title: String);
pub fn set_title(&mut self, id: WindowId, title: Txt);
/// Set window visible.
pub fn set_visible(&mut self, id: WindowId, visible: bool);
@ -393,12 +393,12 @@ declare_api! {
/// Returns a list of image decoders supported by this implementation.
///
/// Each string is the lower-case file extension.
pub fn image_decoders(&mut self) -> Vec<String>;
pub fn image_decoders(&mut self) -> Vec<Txt>;
/// Returns a list of image encoders supported by this implementation.
///
/// Each string is the lower-case file extension.
pub fn image_encoders(&mut self) -> Vec<String>;
pub fn image_encoders(&mut self) -> Vec<Txt>;
/// Encode the image into the `format`.
///
@ -408,7 +408,7 @@ declare_api! {
/// [`Event::ImageEncoded`] or [`Event::ImageEncodeError`].
///
/// [`image_encoders`]: Api::image_encoders
pub fn encode_image(&mut self, id: ImageId, format: String);
pub fn encode_image(&mut self, id: ImageId, format: Txt);
/// Add a raw font resource to the window renderer.
///

View File

@ -14,6 +14,7 @@ use crate::{
};
use serde::{Deserialize, Serialize};
use std::{fmt, path::PathBuf, sync::Arc};
use zero_ui_txt::Txt;
use zero_ui_units::{DipPoint, PxRect, PxSize};
macro_rules! declare_id {
@ -179,7 +180,7 @@ pub enum Event {
/// Id from the request.
id: WindowId,
/// Error message.
error: String,
error: Txt,
},
/// A frame finished rendering.
@ -261,7 +262,7 @@ pub enum Event {
/// This is usually the `key_modified` char, but is also `'\r'` for `Key::Enter`. On Windows when a dead key was
/// pressed earlier but cannot be combined with the character from this key press, the produced text
/// will consist of two characters: the dead-key-character followed by the character resulting from this key press.
text: String,
text: Txt,
},
/// IME composition event.
Ime {
@ -413,14 +414,14 @@ pub enum Event {
/// The image that failed to decode.
image: ImageId,
/// The error message.
error: String,
error: Txt,
},
/// An image finished encoding.
ImageEncoded {
/// The image that finished encoding.
image: ImageId,
/// The format of the encoded data.
format: String,
format: Txt,
/// The encoded image data.
data: IpcBytes,
},
@ -429,9 +430,9 @@ pub enum Event {
/// The image that failed to encode.
image: ImageId,
/// The encoded format that was requested.
format: String,
format: Txt,
/// The error message.
error: String,
error: Txt,
},
/// An image generated from a rendered frame is ready.

View File

@ -3,6 +3,8 @@ use std::{
time::{Duration, Instant},
};
use zero_ui_txt::Txt;
use crate::{MODE_VAR, SERVER_NAME_VAR, VERSION_VAR};
/// Configuration for starting a view-process.
@ -11,13 +13,13 @@ pub struct ViewConfig {
/// The [`VERSION`] of the API crate in the app-process.
///
/// [`VERSION`]: crate::VERSION
pub version: String,
pub version: Txt,
/// Name of the initial channel used in [`connect_view_process`] to setup the connections to the
/// client app-process.
///
/// [`connect_view_process`]: crate::ipc::connect_view_process
pub server_name: String,
pub server_name: Txt,
/// If the server should consider all window requests, headless window requests.
pub headless: bool,
@ -33,8 +35,8 @@ impl ViewConfig {
if let (Ok(version), Ok(server_name)) = (env::var(VERSION_VAR), env::var(SERVER_NAME_VAR)) {
let headless = env::var(MODE_VAR).map(|m| m == "headless").unwrap_or(false);
Some(ViewConfig {
version,
server_name,
version: Txt::from_str(&version),
server_name: Txt::from_str(&server_name),
headless,
})
} else {
@ -99,8 +101,8 @@ impl ViewConfig {
);
ViewConfig {
version: config[0].to_owned(),
server_name: config[1].to_owned(),
version: Txt::from_str(config[0]),
server_name: Txt::from_str(config[1]),
headless: config[2] == "true",
}
}

View File

@ -4,6 +4,7 @@ use std::fmt;
use serde::{Deserialize, Serialize};
use webrender_api::{ColorF, Epoch, PipelineId, RenderReasons};
use zero_ui_txt::Txt;
use crate::{
access::AccessNodeId,
@ -121,7 +122,7 @@ pub struct HeadlessRequest {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitorInfo {
/// Readable name of the monitor.
pub name: String,
pub name: Txt,
/// Top-left offset of the monitor region in the virtual screen, in pixels.
pub position: PxPoint,
/// Width/height of the monitor region in the virtual screen, in pixels.
@ -395,7 +396,7 @@ pub struct WindowRequest {
/// ID that will identify the new window.
pub id: WindowId,
/// Title text.
pub title: String,
pub title: Txt,
/// Window state, position, size and restore rectangle.
pub state: WindowStateAll,

View File

@ -67,6 +67,7 @@ swgl = { git = "https://github.com/servo/webrender.git", rev = "858962993c5e0606
zero-ui-view-api = { path = "../zero-ui-view-api", default-features = false }
zero-ui-units = { path = "../zero-ui-units" }
zero-ui-txt = { path = "../zero-ui-txt" }
tracing = "0.1"
gleam = "0.15.0" # matches webrender

View File

@ -404,10 +404,11 @@ pub(crate) fn locale_config() -> LocaleConfig {
Foundation::FALSE,
Globalization::{GetUserPreferredUILanguages, MUI_LANGUAGE_NAME},
};
use zero_ui_txt::Txt;
// Try newer WinRT COM API (Windows8+)
if let Ok(r) = GlobalizationPreferences::Languages() {
let r: Vec<String> = r.into_iter().map(|l| l.to_string_lossy()).collect();
let r: Vec<_> = r.into_iter().map(|l| Txt::from_str(&l.to_string_lossy())).collect();
if !r.is_empty() {
return LocaleConfig { langs: r };
}
@ -436,6 +437,6 @@ pub(crate) fn locale_config() -> LocaleConfig {
buffer.pop();
LocaleConfig {
langs: String::from_utf16_lossy(&buffer).split('\0').map(|x| x.to_string()).collect(),
langs: String::from_utf16_lossy(&buffer).split('\0').map(Txt::from_str).collect(),
}
}

View File

@ -2,6 +2,7 @@ use std::{fmt, sync::Arc};
use webrender::api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
use winit::window::Icon;
use zero_ui_txt::{formatx, ToText, Txt};
use zero_ui_units::{Px, PxPoint, PxSize};
use zero_ui_view_api::{
image::{ImageDataFormat, ImageDownscale, ImageId, ImageLoadedData, ImageMaskMode, ImagePpi, ImageRequest},
@ -60,7 +61,7 @@ impl ImageCache {
ImageDataFormat::Bgra8 { size, ppi } => {
let expected_len = size.width.0 as usize * size.height.0 as usize * 4;
if data.len() != expected_len {
Err(format!(
Err(formatx!(
"pixels.len() is not width * height * 4, expected {expected_len}, found {}",
data.len()
))
@ -80,7 +81,7 @@ impl ImageCache {
ImageDataFormat::A8 { size } => {
let expected_len = size.width.0 as usize * size.height.0 as usize;
if data.len() != expected_len {
Err(format!(
Err(formatx!(
"pixels.len() is not width * height, expected {expected_len}, found {}",
data.len()
))
@ -101,7 +102,7 @@ impl ImageCache {
Ok((fmt, size)) => {
let decoded_len = size.width.0 as u64 * size.height.0 as u64 * 4;
if decoded_len > max_decoded_len {
Err(format!(
Err(formatx!(
"image {size:?} needs to allocate {decoded_len} bytes, but max allowed size is {max_decoded_len} bytes",
))
} else {
@ -113,7 +114,7 @@ impl ImageCache {
}));
match Self::image_decode(&data[..], fmt, downscale) {
Ok(img) => Ok(Self::convert_decoded(img, mask)),
Err(e) => Err(e.to_string()),
Err(e) => Err(e.to_text()),
}
}
}
@ -172,7 +173,7 @@ impl ImageCache {
size = Some(s);
None
}
ImageDataFormat::FileExtension(ext) => image::ImageFormat::from_extension(ext),
ImageDataFormat::FileExtension(ext) => image::ImageFormat::from_extension(ext.as_str()),
ImageDataFormat::MimeType(t) => t.strip_prefix("image/").and_then(image::ImageFormat::from_extension),
ImageDataFormat::Unknown => None,
};
@ -194,7 +195,7 @@ impl ImageCache {
if let Some(s) = size {
let decoded_len = s.width.0 as u64 * s.height.0 as u64 * 4;
if decoded_len > max_decoded_len {
let error = format!(
let error = formatx!(
"image {size:?} needs to allocate {decoded_len} bytes, but max allowed size is {max_decoded_len} bytes",
);
let _ = app_sender.send(AppEvent::Notify(Event::ImageLoadError { image: id, error }));
@ -229,7 +230,7 @@ impl ImageCache {
Err(e) => {
let _ = app_sender.send(AppEvent::Notify(Event::ImageLoadError {
image: id,
error: e.to_string(),
error: e.to_text(),
}));
}
}
@ -247,7 +248,7 @@ impl ImageCache {
} else {
let _ = app_sender.send(AppEvent::Notify(Event::ImageLoadError {
image: id,
error: "unknown format".to_string(),
error: Txt::from_static("unknown format"),
}));
}
});
@ -287,9 +288,9 @@ impl ImageCache {
let _ = self.app_sender.send(AppEvent::Notify(Event::ImageLoaded(data)));
}
fn get_format_and_size(fmt: &ImageDataFormat, data: &[u8]) -> Result<(image::ImageFormat, PxSize), String> {
fn get_format_and_size(fmt: &ImageDataFormat, data: &[u8]) -> Result<(image::ImageFormat, PxSize), Txt> {
let fmt = match fmt {
ImageDataFormat::FileExtension(ext) => image::ImageFormat::from_extension(ext),
ImageDataFormat::FileExtension(ext) => image::ImageFormat::from_extension(ext.as_str()),
ImageDataFormat::MimeType(t) => t.strip_prefix("image/").and_then(image::ImageFormat::from_extension),
ImageDataFormat::Unknown => None,
ImageDataFormat::Bgra8 { .. } => unreachable!(),
@ -300,7 +301,7 @@ impl ImageCache {
Some(fmt) => image::io::Reader::with_format(std::io::Cursor::new(data), fmt),
None => image::io::Reader::new(std::io::Cursor::new(data))
.with_guessed_format()
.map_err(|e| e.to_string())?,
.map_err(|e| e.to_text())?,
};
match reader.format() {
@ -308,7 +309,7 @@ impl ImageCache {
let (w, h) = reader.into_dimensions().map_err(|e| e.to_string())?;
Ok((fmt, PxSize::new(Px(w as i32), Px(h as i32))))
}
None => Err("unknown format".to_string()),
None => Err(Txt::from_static("unknown format")),
}
}
@ -800,9 +801,9 @@ impl ImageCache {
)
}
pub fn encode(&self, id: ImageId, format: String) {
pub fn encode(&self, id: ImageId, format: Txt) {
if !ENCODERS.contains(&format.as_str()) {
let error = format!("cannot encode `{id:?}` to `{format}`, unknown format");
let error = formatx!("cannot encode `{id:?}` to `{format}`, unknown format");
let _ = self
.app_sender
.send(AppEvent::Notify(Event::ImageEncodeError { image: id, format, error }));
@ -810,7 +811,7 @@ impl ImageCache {
}
if let Some(img) = self.get(id) {
let fmt = image::ImageFormat::from_extension(&format).unwrap();
let fmt = image::ImageFormat::from_extension(format.as_str()).unwrap();
debug_assert!(fmt.can_write());
let img = img.clone();
@ -826,13 +827,13 @@ impl ImageCache {
}));
}
Err(e) => {
let error = format!("failed to encode `{id:?}` to `{format}`, {e}");
let error = formatx!("failed to encode `{id:?}` to `{format}`, {e}");
let _ = sender.send(AppEvent::Notify(Event::ImageEncodeError { image: id, format, error }));
}
}
})
} else {
let error = format!("cannot encode `{id:?}` to `{format}`, image not found");
let error = formatx!("cannot encode `{id:?}` to `{format}`, image not found");
let _ = self
.app_sender
.send(AppEvent::Notify(Event::ImageEncodeError { image: id, format, error }));
@ -1197,6 +1198,7 @@ mod capture {
api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat},
Renderer,
};
use zero_ui_txt::formatx;
use zero_ui_units::{Factor, PxRect};
use zero_ui_view_api::{
image::{ImageDataFormat, ImageId, ImageLoadedData, ImageMaskMode, ImagePpi, ImageRequest},
@ -1230,7 +1232,7 @@ mod capture {
let id = self.image_id_gen.incr();
let _ = self.app_sender.send(AppEvent::Notify(Event::ImageLoadError {
image: id,
error: format!("no frame rendered in window `{window_id:?}`"),
error: formatx!("no frame rendered in window `{window_id:?}`"),
}));
let _ = self.app_sender.send(AppEvent::Notify(Event::FrameImageReady {
window: window_id,

View File

@ -139,6 +139,7 @@ pub use gleam;
use webrender::api::*;
use window::Window;
use zero_ui_txt::Txt;
use zero_ui_units::{Dip, DipPoint, DipRect, DipSize, Factor, Px, PxPoint, PxRect, PxToDip};
use zero_ui_view_api::{
api_extension::{ApiExtensionId, ApiExtensionPayload},
@ -940,7 +941,7 @@ impl App {
state,
key,
key_modified,
text: event.text.map(|s| s.as_str().to_owned()).unwrap_or_default(),
text: event.text.map(|s| Txt::from_str(s.as_str())).unwrap_or_default(),
});
}
}
@ -1180,7 +1181,7 @@ impl App {
state: KeyState::Released,
key: key.clone(),
key_modified: key.clone(),
text: String::new(),
text: Txt::from_str(""),
});
}
if matches!(key, Key::Shift) && !m.shift_key() {
@ -1192,7 +1193,7 @@ impl App {
state: KeyState::Released,
key: key.clone(),
key_modified: key.clone(),
text: String::new(),
text: Txt::from_str(""),
});
}
if matches!(key, Key::Alt | Key::AltGraph) && !m.alt_key() {
@ -1204,7 +1205,7 @@ impl App {
state: KeyState::Released,
key: key.clone(),
key_modified: key.clone(),
text: String::new(),
text: Txt::from_str(""),
});
}
if matches!(key, Key::Ctrl) && !m.control_key() {
@ -1216,7 +1217,7 @@ impl App {
state: KeyState::Released,
key: key.clone(),
key_modified: key.clone(),
text: String::new(),
text: Txt::from_str(""),
});
}
retain
@ -1623,7 +1624,7 @@ impl Api for App {
}
}
fn set_title(&mut self, id: WindowId, title: String) {
fn set_title(&mut self, id: WindowId, title: Txt) {
self.with_window(id, |w| w.set_title(title), || ())
}
@ -1719,12 +1720,12 @@ impl Api for App {
self.with_window(id, |w| w.set_ime_area(area), || ())
}
fn image_decoders(&mut self) -> Vec<String> {
image_cache::DECODERS.iter().map(|&s| s.to_owned()).collect()
fn image_decoders(&mut self) -> Vec<Txt> {
image_cache::DECODERS.iter().map(|&s| Txt::from_static(s)).collect()
}
fn image_encoders(&mut self) -> Vec<String> {
image_cache::ENCODERS.iter().map(|&s| s.to_owned()).collect()
fn image_encoders(&mut self) -> Vec<Txt> {
image_cache::ENCODERS.iter().map(|&s| Txt::from_static(s)).collect()
}
fn add_image(&mut self, request: ImageRequest<IpcBytes>) -> ImageId {
@ -1739,7 +1740,7 @@ impl Api for App {
self.image_cache.forget(id)
}
fn encode_image(&mut self, id: ImageId, format: String) {
fn encode_image(&mut self, id: ImageId, format: Txt) {
self.image_cache.encode(id, format)
}
@ -1823,7 +1824,7 @@ impl Api for App {
if let Some(s) = self.windows.iter_mut().find(|s| s.id() == id) {
s.message_dialog(dialog, r_id, self.app_sender.clone());
} else {
let r = MsgDialogResponse::Error("window not found".to_owned());
let r = MsgDialogResponse::Error(Txt::from_static("window not found"));
let _ = self.app_sender.send(AppEvent::Notify(Event::MsgDialogResponse(r_id, r)));
}
r_id
@ -1834,7 +1835,7 @@ impl Api for App {
if let Some(s) = self.windows.iter_mut().find(|s| s.id() == id) {
s.file_dialog(dialog, r_id, self.app_sender.clone());
} else {
let r = MsgDialogResponse::Error("window not found".to_owned());
let r = MsgDialogResponse::Error(Txt::from_static("window not found"));
let _ = self.app_sender.send(AppEvent::Notify(Event::MsgDialogResponse(r_id, r)));
};
r_id
@ -1848,7 +1849,7 @@ impl Api for App {
clipboard_win::get(clipboard_win::formats::Unicode)
.map_err(util::clipboard_win_to_clip)
.map(clipboard::ClipboardData::Text)
.map(|s: String| clipboard::ClipboardData::Text(Txt::from_str(&s)))
}
clipboard::ClipboardType::Image => {
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
@ -1856,7 +1857,7 @@ impl Api for App {
let bitmap = clipboard_win::get(clipboard_win::formats::Bitmap).map_err(util::clipboard_win_to_clip)?;
let id = self.image_cache.add(ImageRequest {
format: image::ImageDataFormat::FileExtension("bmp".to_owned()),
format: image::ImageDataFormat::FileExtension(Txt::from_str("bmp")),
data: IpcBytes::from_vec(bitmap),
max_decoded_len: u64::MAX,
downscale: None,
@ -1877,6 +1878,8 @@ impl Api for App {
#[cfg(windows)]
fn write_clipboard(&mut self, data: clipboard::ClipboardData) -> Result<(), clipboard::ClipboardError> {
use zero_ui_txt::formatx;
match data {
clipboard::ClipboardData::Text(t) => {
let _clip = clipboard_win::Clipboard::new_attempts(10).map_err(util::clipboard_win_to_clip)?;
@ -1889,10 +1892,10 @@ impl Api for App {
if let Some(img) = self.image_cache.get(id) {
let mut bmp = vec![];
img.encode(::image::ImageFormat::Bmp, &mut bmp)
.map_err(|e| clipboard::ClipboardError::Other(format!("{e:?}")))?;
.map_err(|e| clipboard::ClipboardError::Other(formatx!("{e:?}")))?;
clipboard_win::set(clipboard_win::formats::Bitmap, bmp).map_err(util::clipboard_win_to_clip)
} else {
Err(clipboard::ClipboardError::Other("image not found".to_owned()))
Err(clipboard::ClipboardError::Other(Txt::from_str("image not found")))
}
}
clipboard::ClipboardData::FileList(l) => {

View File

@ -2,6 +2,7 @@ use std::{cell::Cell, sync::Arc};
use rayon::ThreadPoolBuilder;
use winit::{event::ElementState, monitor::MonitorHandle};
use zero_ui_txt::Txt;
use zero_ui_units::*;
use zero_ui_view_api::access::AccessNodeId;
use zero_ui_view_api::clipboard as clipboard_api;
@ -251,7 +252,7 @@ pub(crate) fn monitor_handle_to_info(handle: &MonitorHandle) -> MonitorInfo {
let position = handle.position().to_px();
let size = handle.size().to_px();
MonitorInfo {
name: handle.name().unwrap_or_default(),
name: Txt::from_str(&handle.name().unwrap_or_default()),
position,
size,
scale_factor: Factor(handle.scale_factor() as _),
@ -1035,13 +1036,15 @@ pub(crate) fn arboard_to_clip(e: arboard::Error) -> clipboard_api::ClipboardErro
#[cfg(windows)]
pub(crate) fn clipboard_win_to_clip(e: clipboard_win::SystemError) -> clipboard_api::ClipboardError {
use zero_ui_txt::formatx;
if e == clipboard_win::SystemError::unimplemented() {
clipboard_api::ClipboardError::NotSupported
} else if e.is_zero() {
// If GetClipboardData fails it returns a NULL, but GetLastError sometimes (always?) returns 0 (ERROR_SUCCESS)
clipboard_api::ClipboardError::NotFound
} else {
clipboard_api::ClipboardError::Other(format!("{e:?}"))
clipboard_api::ClipboardError::Other(formatx!("{e:?}"))
}
}
@ -1080,9 +1083,9 @@ pub(crate) fn accesskit_to_event(
Action::ShowTooltip => AccessCmd::SetToolTipVis(true),
Action::ReplaceSelectedText => {
if let Some(accesskit::ActionData::Value(s)) = request.data {
AccessCmd::ReplaceSelectedText(s.to_string())
AccessCmd::ReplaceSelectedText(Txt::from_str(&s))
} else {
AccessCmd::ReplaceSelectedText(String::new())
AccessCmd::ReplaceSelectedText(Txt::from_str(""))
}
}
Action::ScrollBackward => AccessCmd::Scroll(ScrollCmd::PageUp),
@ -1128,7 +1131,7 @@ pub(crate) fn accesskit_to_event(
}
}
Action::SetValue => match request.data {
Some(accesskit::ActionData::Value(s)) => AccessCmd::SetString(s.to_string()),
Some(accesskit::ActionData::Value(s)) => AccessCmd::SetString(Txt::from_str(&s)),
Some(accesskit::ActionData::NumericValue(n)) => AccessCmd::SetNumber(n),
_ => return None,
},
@ -1284,7 +1287,7 @@ fn access_node_to_kit(
builder.set_invalid(accesskit::Invalid::True)
}
}
Label(s) => builder.set_name(s.clone().into_boxed_str()),
Label(s) => builder.set_name(s.clone().into_owned().into_boxed_str()),
Level(n) => builder.set_hierarchical_level(n.get() as usize),
Modal => builder.set_modal(),
MultiSelectable => builder.set_multiselectable(),
@ -1292,7 +1295,7 @@ fn access_node_to_kit(
access::Orientation::Horizontal => builder.set_orientation(accesskit::Orientation::Horizontal),
access::Orientation::Vertical => builder.set_orientation(accesskit::Orientation::Vertical),
},
Placeholder(p) => builder.set_placeholder(p.clone().into_boxed_str()),
Placeholder(p) => builder.set_placeholder(p.clone().into_owned().into_boxed_str()),
ReadOnly => builder.set_read_only(),
Required => builder.set_required(),
Selected => builder.set_selected(true),
@ -1303,7 +1306,7 @@ fn access_node_to_kit(
ValueMax(m) => builder.set_max_numeric_value(*m),
ValueMin(m) => builder.set_min_numeric_value(*m),
Value(v) => builder.set_numeric_value(*v),
ValueText(v) => builder.set_value(v.clone().into_boxed_str()),
ValueText(v) => builder.set_value(v.clone().into_owned().into_boxed_str()),
Live { indicator, atomic, busy } => {
builder.set_live(match indicator {
access::LiveIndicator::Assertive => accesskit::Live::Assertive,

View File

@ -24,6 +24,7 @@ use winit::{
monitor::{MonitorHandle, VideoMode as GVideoMode},
window::{Fullscreen, Icon, Window as GWindow, WindowBuilder},
};
use zero_ui_txt::Txt;
use zero_ui_units::{Dip, DipPoint, DipRect, DipSize, DipToPx, Factor, Px, PxPoint, PxRect, PxToDip, PxVector};
use zero_ui_view_api::{
api_extension::{ApiExtensionId, ApiExtensionPayload},
@ -263,7 +264,7 @@ impl Window {
state: KeyState::Pressed,
key: Key::F4,
key_modified: Key::F4,
text: String::new(),
text: Txt::from_static(""),
}));
return Some(0);
}
@ -470,7 +471,7 @@ impl Window {
self.rendered_frame_id
}
pub fn set_title(&self, title: String) {
pub fn set_title(&self, title: Txt) {
self.window.set_title(&title);
}
@ -1533,7 +1534,7 @@ impl Window {
if already_open {
let _ = event_sender.send(AppEvent::Notify(Event::MsgDialogResponse(
id,
dlg_api::MsgDialogResponse::Error("dialog already open".to_owned()),
dlg_api::MsgDialogResponse::Error(Txt::from_static("dialog already open")),
)));
}
already_open
@ -1556,8 +1557,8 @@ impl Window {
dlg_api::MsgDialogButtons::OkCancel => rfd::MessageButtons::OkCancel,
dlg_api::MsgDialogButtons::YesNo => rfd::MessageButtons::YesNo,
})
.set_title(&dialog.title)
.set_description(&dialog.message)
.set_title(dialog.title.as_str())
.set_description(dialog.message.as_str())
.set_parent(&self.window);
let modal_dialog_active = self.modal_dialog_active.clone();
@ -1593,9 +1594,9 @@ impl Window {
}
let mut dlg = rfd::AsyncFileDialog::new()
.set_title(&dialog.title)
.set_title(dialog.title.as_str())
.set_directory(&dialog.starting_dir)
.set_file_name(&dialog.starting_name)
.set_file_name(dialog.starting_name.as_str())
.set_parent(&self.window);
for (name, patterns) in dialog.iter_filters() {
dlg = dlg.add_filter(name, &patterns.map(|s| s.trim_start_matches(['*', '.'])).collect::<Vec<_>>());

View File

@ -42,7 +42,7 @@ fn on_build(wgt: &mut WidgetBuilding) {
wgt.set_child(node);
let source = wgt.capture_var::<ImageSource>(property_id!(source)).unwrap_or_else(|| {
let error = Img::dummy(Some("no source".to_owned()));
let error = Img::dummy(Some(Txt::from_static("no source")));
let error = ImageSource::Image(var(error).read_only());
LocalVar(error).boxed()
});
@ -60,7 +60,7 @@ mod tests {
fn error_view_recursion() {
crate::core::test_log();
let img = var(crate::core::image::Img::dummy(Some("test error".to_string()))).read_only();
let img = var(crate::core::image::Img::dummy(Some(Txt::from_static("test error")))).read_only();
let mut app = App::default().run_headless(false);
IMAGES.load_in_headless().set(true);

View File

@ -18,7 +18,7 @@ context_var! {
pub static CONTEXT_IMAGE_VAR: Img = no_context_image();
}
fn no_context_image() -> Img {
Img::dummy(Some("no image source in context".to_owned()))
Img::dummy(Some(Txt::from_static("no image source in context")))
}
/// Requests an image from [`IMAGES`] and sets [`CONTEXT_IMAGE_VAR`].