Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 85 additions & 85 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ metadata.makepad-auto-version = "zqpv-Yj-K7WNVK2I8h5Okhho46Q="
# makepad-widgets = { git = "https://github.com/makepad/makepad", branch = "dev", features = ["serde"] }
# makepad-code-editor = { git = "https://github.com/makepad/makepad", branch = "dev" }

makepad-widgets = { git = "https://github.com/kevinaboos/makepad", branch = "ime_composition_preview", features = ["serde"] }
makepad-code-editor = { git = "https://github.com/kevinaboos/makepad", branch = "ime_composition_preview" }
makepad-widgets = { git = "https://github.com/kevinaboos/makepad", branch = "gl_sampling_fallback", features = ["serde"] }
makepad-code-editor = { git = "https://github.com/kevinaboos/makepad", branch = "gl_sampling_fallback" }

# makepad-widgets = { path = "../makepad/widgets", features = ["serde"] }
# makepad-code-editor = { path = "../makepad/code_editor" }

## Including this crate automatically configures all `robius-*` crates to work with Makepad.
robius-use-makepad = "0.1.1"
Expand Down Expand Up @@ -181,8 +183,8 @@ winresource = "0.1"
all-features = true

[profile.release]
debug = false
strip = "symbols"
debug = true
# strip = "symbols"

## An optimized profile for development, with full debug info and assertions.
[profile.debug-opt]
Expand Down
8 changes: 5 additions & 3 deletions resources/icons/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions resources/icons/upload.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion src/home/light_themed_dock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,14 @@ script_mod! {
draw_bg +: {
// Light blue-ish color, de-saturated from COLOR_ACTIVE_PRIMARY
color: #E1EEFA
color_2: #E1EEFA
// A slightly darker shade of the tab color for hover visibility
color_hover: #C8DDEF
color_active: COLOR_ACTIVE_PRIMARY
color_2_hover: #C8DDEF
// Active (selected) tabs are a deeper blue, with a vertical gradient
// to a slightly lighter blue.
color_active: #0660FE
color_2_active: #398CFE
// Remove the border and rounded corners from the default Tab style
border_size: 0.0
border_radius: 3.0
Expand Down
6 changes: 4 additions & 2 deletions src/home/navigation_tab_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ script_mod! {
margin: 0,
icon_walk: Walk {
margin: 0,
width: 30,
height: 30
width: 27,
height: 27
}
draw_icon +: {
color: (COLOR_NAVIGATION_TAB_FG)
Expand Down Expand Up @@ -173,6 +173,7 @@ script_mod! {

mod.widgets.NavigationTabBar = #(NavigationTabBar::register_widget(vm)) {
Desktop := RoundedView {
new_batch: true,
flow: Down,
align: Align{x: 0.5}
padding: Inset{
Expand Down Expand Up @@ -206,6 +207,7 @@ script_mod! {
}

Mobile := RoundedView {
new_batch: true,
flow: Right
align: Align{x: 0.5, y: 0.5}
width: Fill,
Expand Down
4 changes: 3 additions & 1 deletion src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ script_mod! {
height: Fill,
align: Align{x: 0.5, y: 0.0} // center horizontally, align to top vertically
flow: Overlay,
new_batch: true

list := PortalList {
height: Fill,
Expand Down Expand Up @@ -4239,8 +4240,9 @@ fn populate_image_message_content(
let mut fetch_and_show_media_source = |cx: &mut Cx, media_source: MediaSource, image_info: Box<ImageInfo>| {
match media_cache.try_get_media_or_fetch(&media_source, MEDIA_THUMBNAIL_FORMAT.into()) {
(MediaCacheEntry::Loaded(data), _media_format) => {
let cache_key = media_source_mxc(&media_source).to_string();
let show_image_result = text_or_image_ref.show_image(cx, Some(media_source), |cx, img| {
utils::load_image(&img, cx, &data)
img.load_image_from_data_async(cx, std::path::Path::new(&cache_key), Arc::clone(&data))
.map(|()| img.size_in_pixels(cx).unwrap_or_default())
});
if let Err(e) = show_image_result {
Expand Down
8 changes: 8 additions & 0 deletions src/home/rooms_list_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ script_mod! {
height: Fit
flow: Overlay
align: Align{ x: 1.0 }
// Don't clip the unread badge's glow to this container's bounds.
clip_x: false, clip_y: false
avatar := Avatar {}
unread_badge := UnreadBadge {}
tombstone_icon := mod.widgets.TombstoneIcon {}
Expand All @@ -200,6 +202,8 @@ script_mod! {
flow: Down
width: Fill, height: 56
align: Align{ x: 0.0, y: 0.0 }
// Fixed-height view: don't clip the unread badge's glow off the top/bottom.
clip_x: false, clip_y: false
top := View {
width: Fill, height: Fit,
spacing: 3,
Expand All @@ -211,12 +215,16 @@ script_mod! {
width: Fill, height: Fill,
spacing: 2,
flow: Right,
// Don't clip the unread badge's glow off the top/bottom.
clip_x: false, clip_y: false
preview := mod.widgets.MessagePreview {
margin: Inset{ top: 2.5 }
}
View {
width: Fit, height: Fit
align: Align{ x: 1.0 }
// Don't clip the unread badge's glow to this container's bounds.
clip_x: false, clip_y: false
unread_badge := UnreadBadge {}
tombstone_icon := mod.widgets.TombstoneIcon {}
}
Expand Down
1 change: 1 addition & 0 deletions src/room/room_input_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ script_mod! {
width: Fill,
height: Fit{max: FitBound.Rel{base: Base.Full, factor: 0.75}}
flow: Down,
new_batch: true

// These margins are a hack to make the borders of the RoomInputBar
// line up with the boundaries of its parent widgets.
Expand Down
29 changes: 21 additions & 8 deletions src/shared/avatar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,15 +301,16 @@ impl Avatar {
.or_else(try_get_cached_username_avatar)
.unwrap_or((None, AvatarState::Unknown));

let (avatar_img_data_opt, profile_drawn) = match avatar_state {
AvatarState::Loaded(data) => (Some(data), true),
let (avatar_img_data_opt, profile_drawn, avatar_key) = match avatar_state {
AvatarState::Loaded(data) => (Some(data), true, None),
AvatarState::Known(Some(uri)) => match avatar_cache::get_or_fetch_avatar(cx, &uri) {
AvatarCacheEntry::Loaded(data) => (Some(data), true),
AvatarCacheEntry::Failed => (None, true),
AvatarCacheEntry::Requested => (None, false),
// Use the avatar's MxcUri as a key for makepad's async image cache
AvatarCacheEntry::Loaded(data) => (Some(data), true, Some(uri.to_string())),
AvatarCacheEntry::Failed => (None, true, None),
AvatarCacheEntry::Requested => (None, false, None),
},
AvatarState::Known(None) | AvatarState::Failed => (None, true),
AvatarState::Unknown => (None, false),
AvatarState::Known(None) | AvatarState::Failed => (None, true, None),
AvatarState::Unknown => (None, false, None),
};

// Set sender to the display name if available, otherwise the user id.
Expand All @@ -328,7 +329,19 @@ impl Avatar {
timeline_kind.room_id().to_owned(),
data.clone()
))),
|cx, img| utils::load_image(&img, cx, &data),
|cx, img| {
if let Some(key) = avatar_key.as_deref() {
// Try to decode the image data asynchronously, which we can only do
// if we have a key for the image cache (the avatar's MxcUri).
img.load_image_from_data_async(
cx,
std::path::Path::new(key),
Arc::clone(&data),
)
} else {
utils::load_image_cached(&img, cx, Arc::clone(&data))
}
}
)
.ok()
})
Expand Down
2 changes: 1 addition & 1 deletion src/shared/html_or_plaintext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ impl MatrixLinkPill {
let res = avatar_ref.show_image(
cx,
None, // Don't make this avatar clickable
|cx, img_ref| utils::load_image(&img_ref, cx, &data),
|cx, img_ref| utils::load_image_cached(&img_ref, cx, Arc::clone(&data)),
);
if res.is_ok() {
return;
Expand Down
67 changes: 52 additions & 15 deletions src/shared/unread_badge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,62 @@ script_mod! {

mod.widgets.UnreadBadge = #(UnreadBadge::register_widget(vm)) {

width: 30, height: 20,
width: 27, height: 18,
align: Align{ x: 0.5, y: 0.5 }
flow: Overlay,
// Let the badge's fade-out/glow effect render beyond the badge's rect.
clip_x: false,
clip_y: false,

rounded_view := View {
width: Fill,
height: Fill,
show_bg: true,
clip_x: false,
clip_y: false,

draw_bg +: {
badge_color: instance((COLOR_UNREAD_BADGE_MESSAGES)),
border_radius: instance(4.0)
// Set this border_size to a larger value to make the oval smaller
// A larger border size results in a smaller oval
border_size: instance(2.0)
// For unread mention badges only, we fade through a lighter color to reduce aliasing effects
// on lower-res screens, since red on purple looks blocky/pixellated otherwise.
fade_color: instance(#xFFC8B0)
fade_radius: uniform(5.0)
// Controls the transition of the outer border.
// 0.0 is a crisp solid badge, 1.0 is a soft fading/dissolve transition.
soft: instance(0.0)

pixel: fn() {
let sdf = Sdf2d.viewport(self.pos * self.rect_size)
sdf.box(
self.border_size,
1.0,
self.rect_size.x - (self.border_size * 2.0),
self.rect_size.y - 2.0,
max(1.0, self.border_radius)
vertex: fn() {
let m = self.fade_radius
return self.clip_and_transform_vertex(
self.rect_pos - vec2(m),
self.rect_size + vec2(m * 2.0)
)
sdf.fill_keep(self.badge_color);
}

pixel: fn() {
let m = self.fade_radius
let rs3 = self.rect_size + vec2(m * 2.0)
let sdf = Sdf2d.viewport(self.pos * rs3)
let bw = self.rect_size.x - (self.border_size * 2.0)
let bh = self.rect_size.y - 2.0
let bx = m + self.border_size
let by = m + 1.0
let rad = max(1.0, self.border_radius)
sdf.box(bx, by, bw, bh, rad)
let dist = sdf.shape
let half = bh * 0.5
let aa = clamp(0.5 - dist, 0.0, 1.0)
let band_start = -half * 0.45
let t = clamp((dist - band_start) / (m - band_start), 0.0, 1.0)
let s = t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
let warm = mix(self.badge_color.rgb, self.fade_color.rgb, s)
let dissolve = 1.0 - s
let color = mix(self.badge_color.rgb, warm, self.soft)
let alpha = mix(aa, dissolve, self.soft)
sdf.clear(vec4(color, alpha))
return sdf.result;
}
}
Expand Down Expand Up @@ -94,7 +126,10 @@ impl Widget for UnreadBadge {
script_apply_eval!(cx, rounded_view, {
draw_bg +: {
border_size: #(border_size),
badge_color: mod.widgets.COLOR_UNREAD_BADGE_MENTIONS
// Solid red core fading out through a lighter warm color.
badge_color: #xFF1133,
fade_color: #xFFC8B0,
soft: 1.0
}
});
self.visible = true;
Expand All @@ -105,8 +140,9 @@ impl Widget for UnreadBadge {
let mut rounded_view = self.view(cx, ids!(rounded_view));
script_apply_eval!(cx, rounded_view, {
draw_bg +: {
border_size: 6.0, // larger value = smaller dot
badge_color: mod.widgets.COLOR_UNREAD_BADGE_MARKED
border_size: 6.0, // larger value = smaller badge size
badge_color: mod.widgets.COLOR_UNREAD_BADGE_MARKED,
soft: 0.0
}
});
self.visible = true;
Expand All @@ -120,7 +156,8 @@ impl Widget for UnreadBadge {
script_apply_eval!(cx, rounded_view, {
draw_bg +: {
border_size: #(border_size),
badge_color: mod.widgets.COLOR_UNREAD_BADGE_MESSAGES
badge_color: mod.widgets.COLOR_UNREAD_BADGE_MESSAGES,
soft: 0.0
}
});
self.visible = true;
Expand Down
38 changes: 35 additions & 3 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,37 @@ pub fn is_supported_image_mimetype(mimetype: &str) -> bool {
///
/// Returns an error if the format is unsupported or decoding fails.
pub fn load_image(img: &ImageRef, cx: &mut Cx, data: &[u8]) -> Result<(), ImageError> {
img.load_image_from_data(cx, data)
load_image_cached(img, cx, std::sync::Arc::<[u8]>::from(data))
}

/// Returns a cache key for the given encoded image `data`.
pub fn image_cache_key(data: &[u8]) -> std::path::PathBuf {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let len = data.len();
// Hash only the length plus a small sample of the bytes.
const SAMPLE: usize = 256;
len.hash(&mut hasher);
if len <= 2 * SAMPLE {
data.hash(&mut hasher);
} else {
data[..SAMPLE].hash(&mut hasher);
data[len - SAMPLE..].hash(&mut hasher);
}
std::path::PathBuf::from(format!("robrix-img-cache://{:016x}-{}", hasher.finish(), len))
}

/// Loads the encoded image `data` into the given ImageRef widget using makepad's async image cache.
pub fn load_image_cached<D>(
img: &ImageRef,
cx: &mut Cx,
data: std::sync::Arc<D>,
) -> Result<(), ImageError>
where
D: AsRef<[u8]> + Send + Sync + ?Sized + 'static,
{
let key = image_cache_key(data.as_ref().as_ref());
img.load_image_from_data_async(cx, &key, data)
}


Expand Down Expand Up @@ -511,8 +541,10 @@ impl From<MediaThumbnailSettingsConst> for MediaThumbnailSettings {
pub const AVATAR_THUMBNAIL_FORMAT: MediaFormatConst = MediaFormatConst::Thumbnail(
MediaThumbnailSettingsConst {
method: Method::Scale,
width: 40,
height: 40,
// while we typically show avatars at around 40x40,
// fetching a higher quality one is needed for good downscaling.
width: 192,
height: 192,
animated: false,
}
);
Expand Down