diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in index c19463099..d45cba319 100644 --- a/data/org.gnome.shell.gschema.xml.in +++ b/data/org.gnome.shell.gschema.xml.in @@ -101,11 +101,13 @@ take care of its own output - this might be used to send the output to an icecast server via shout2send or similar. When unset or set to an empty value, the default pipeline will be used. This is currently - 'videorate ! theoraenc ! oggmux' and records to Ogg Theora. + 'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' + and records to WEBM using the VP8 codec. %T is used as a placeholder + for a guess at the optimal thread count on the system. - 'ogv' + 'webm' <_summary>File extension used for storing the screencast <_description> The filename for recorded screencasts will be a unique filename diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 0d8d2a53e..4c1a67915 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -313,7 +313,7 @@ StTooltip StLabel { background-image: url("close-window.svg"); height: 24px; width: 24px; - -st-shadow: -2px 2px 6px rgba(0,0,0,0.5); + -st-background-image-shadow: -2px 2px 6px rgba(0,0,0,0.5); -shell-close-overlap: 16px; } @@ -374,7 +374,7 @@ StTooltip StLabel { background-gradient-direction: vertical; color: rgb(64, 64, 64); font-weight: bold; - -st-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); + box-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); transition-duration: 0; } @@ -391,6 +391,10 @@ StTooltip StLabel { height: 24px; } +.view-tab-title:hover { + color: #bbb; +} + .view-tab-title:selected { color: #000000; background-color: #c2c7cd; @@ -506,7 +510,7 @@ StTooltip StLabel { .remove-favorite-icon:hover { color: white; - -st-shadow: black 0px 2px 2px; + icon-shadow: black 0px 2px 2px; } .app-well-app > .overview-icon, @@ -1004,7 +1008,7 @@ StTooltip StLabel { color: #545454; background-color: #e8e8e8; caret-color: #545454; - -st-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); + box-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9); } /* The spacing and padding on the summary is tricky; we want to keep diff --git a/js/ui/altTab.js b/js/ui/altTab.js index aa0e7e246..a8d3e79ea 100644 --- a/js/ui/altTab.js +++ b/js/ui/altTab.js @@ -15,6 +15,8 @@ const POPUP_APPICON_SIZE = 96; const POPUP_SCROLL_TIME = 0.10; // seconds const POPUP_FADE_TIME = 0.1; // seconds +const APP_ICON_HOVER_TIMEOUT = 750; // milliseconds + const DISABLE_HOVER_TIMEOUT = 500; // milliseconds const THUMBNAIL_DEFAULT_SIZE = 256; @@ -50,6 +52,8 @@ AltTabPopup.prototype = { this._thumbnailTimeoutId = 0; this._motionTimeoutId = 0; + this.thumbnailsVisible = false; + // Initially disable hover so we ignore the enter-event if // the switcher appears underneath the current pointer location this._disableHover(); @@ -133,7 +137,7 @@ AltTabPopup.prototype = { this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside)); this.actor.connect('scroll-event', Lang.bind(this, this._onScroll)); - this._appSwitcher = new AppSwitcher(apps); + this._appSwitcher = new AppSwitcher(apps, this); this.actor.add_actor(this._appSwitcher.actor); this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated)); this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered)); @@ -457,11 +461,15 @@ AltTabPopup.prototype = { }, _destroyThumbnails : function() { - Tweener.addTween(this._thumbnails.actor, + let thumbnailsActor = this._thumbnails.actor; + Tweener.addTween(thumbnailsActor, { opacity: 0, time: THUMBNAIL_FADE_TIME, transition: 'easeOutQuad', - onComplete: function() { this.destroy(); } + onComplete: Lang.bind(this, function() { + thumbnailsActor.destroy(); + this.thumbnailsVisible = false; + }) }); this._thumbnails = null; }, @@ -477,7 +485,8 @@ AltTabPopup.prototype = { Tweener.addTween(this._thumbnails.actor, { opacity: 255, time: THUMBNAIL_FADE_TIME, - transition: 'easeOutQuad' + transition: 'easeOutQuad', + onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; }) }); } }; @@ -591,16 +600,20 @@ SwitcherList.prototype = { this._list.add_actor(bbox); let n = this._items.length; - bbox.connect('clicked', Lang.bind(this, function () { - this._itemActivated(n); - })); - bbox.connect('enter-event', Lang.bind(this, function () { - this._itemEntered(n); - })); + bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); })); + bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); })); this._items.push(bbox); }, + _onItemClicked: function (index) { + this._itemActivated(index); + }, + + _onItemEnter: function (index) { + this._itemEntered(index); + }, + addSeparator: function () { let box = new St.Bin({ style_class: 'separator' }); this._separator = box; @@ -819,14 +832,14 @@ AppIcon.prototype = { } }; -function AppSwitcher(apps) { - this._init(apps); +function AppSwitcher(apps, altTabPopup) { + this._init(apps, altTabPopup); } AppSwitcher.prototype = { __proto__ : SwitcherList.prototype, - _init : function(apps) { + _init : function(apps, altTabPopup) { SwitcherList.prototype._init.call(this, true); // Construct the AppIcons, sort by time, add to the popup @@ -858,6 +871,8 @@ AppSwitcher.prototype = { this._curApp = -1; this._iconSize = 0; + this._altTabPopup = altTabPopup; + this._mouseTimeOutId = 0; }, _getPreferredHeight: function (actor, forWidth, alloc) { @@ -922,6 +937,27 @@ AppSwitcher.prototype = { } }, + // We override SwitcherList's _onItemEnter method to delay + // activation when the thumbnail list is open + _onItemEnter: function (index) { + if (this._mouseTimeOutId != 0) + Mainloop.source_remove(this._mouseTimeOutId); + if (this._altTabPopup.thumbnailsVisible) { + this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT, + Lang.bind(this, function () { + this._enterItem(index); + })); + } else + this._itemEntered(index); + }, + + _enterItem: function(index) { + let [x, y, mask] = global.get_pointer(); + let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y); + if (this._items[index].contains(pickedActor)) + this._itemEntered(index); + }, + // We override SwitcherList's highlight() method to also deal with // the AppSwitcher->ThumbnailList arrows. Apps with only 1 window // will hide their arrows by default, but show them when their diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 4b460fcc0..6cefd6496 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -40,9 +40,18 @@ AlphabeticalView.prototype = { this.actor = new St.ScrollView({ x_fill: true, y_fill: false, y_align: St.Align.START, - vshadows: true }); + vfade: true }); this.actor.add_actor(box); this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + this.actor.connect('notify::mapped', Lang.bind(this, + function() { + if (!this.actor.mapped) + return; + + let adjustment = this.actor.vscroll.adjustment; + let direction = Overview.SwipeScrollDirection.VERTICAL; + Main.overview.setScrollAdjustment(adjustment, direction); + })); }, _removeAll: function() { diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index c158c037a..073b87acc 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -386,7 +386,7 @@ Notification.prototype = { this._scrollArea = new St.ScrollView({ name: 'notification-scrollview', vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, hscrollbar_policy: Gtk.PolicyType.NEVER, - vshadows: true }); + vfade: true }); this.actor.add(this._scrollArea, { row: 1, col: 1 }); this._contentArea = new St.BoxLayout({ name: 'notification-body', diff --git a/js/ui/overview.js b/js/ui/overview.js index 35c1b05c4..8f4e830fa 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -1,6 +1,7 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; +const Gtk = imports.gi.Gtk; const Meta = imports.gi.Meta; const Mainloop = imports.mainloop; const Signals = imports.signals; @@ -32,6 +33,18 @@ const DASH_SPLIT_FRACTION = 0.1; const DND_WINDOW_SWITCH_TIMEOUT = 1250; +const SwipeScrollDirection = { + NONE: 0, + HORIZONTAL: 1, + VERTICAL: 2 +}; + +const SwipeScrollResult = { + CANCEL: 0, + SWIPE: 1, + CLICK: 2 +}; + function ShellInfo() { this._init(); } @@ -98,7 +111,8 @@ Overview.prototype = { this._spacing = 0; - this._group = new St.Group({ name: 'overview' }); + this._group = new St.Group({ name: 'overview', + reactive: true }); this._group._delegate = this; this._group.connect('style-changed', Lang.bind(this, function() { @@ -110,6 +124,11 @@ Overview.prototype = { } })); + this._scrollDirection = SwipeScrollDirection.NONE; + this._scrollAdjustment = null; + this._capturedEventId = 0; + this._buttonPressId = 0; + this.shellInfo = new ShellInfo(); this._workspacesDisplay = null; @@ -154,7 +173,7 @@ Overview.prototype = { this._dash.actor.add_constraint(this.viewSelector.constrainY); this._dash.actor.add_constraint(this.viewSelector.constrainHeight); - this._coverPane.lower_bottom(); + this._coverPane.hide(); // XDND this._dragMonitor = { @@ -233,6 +252,149 @@ Overview.prototype = { return DND.DragMotionResult.CONTINUE; }, + setScrollAdjustment: function(adjustment, direction) { + this._scrollAdjustment = adjustment; + if (this._scrollAdjustment == null) + this._scrollDirection = SwipeScrollDirection.NONE; + else + this._scrollDirection = direction; + }, + + _onButtonPress: function(actor, event) { + if (this._scrollDirection == SwipeScrollDirection.NONE) + return; + + let [stageX, stageY] = event.get_coords(); + this._dragStartX = this._dragX = stageX; + this._dragStartY = this._dragY = stageY; + this._dragStartValue = this._scrollAdjustment.value; + this._lastMotionTime = -1; // used to track "stopping" while swipe-scrolling + this._capturedEventId = global.stage.connect('captured-event', + Lang.bind(this, this._onCapturedEvent)); + this.emit('swipe-scroll-begin'); + }, + + _onCapturedEvent: function(actor, event) { + let stageX, stageY; + switch(event.type()) { + case Clutter.EventType.BUTTON_RELEASE: + [stageX, stageY] = event.get_coords(); + + // default to snapping back to the original value + let newValue = this._dragStartValue; + + let minValue = this._scrollAdjustment.lower; + let maxValue = this._scrollAdjustment.upper - this._scrollAdjustment.page_size; + + let direction; + if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) { + direction = stageX > this._dragStartX ? -1 : 1; + if (St.Widget.get_default_direction() == St.TextDirection.RTL) + direction *= -1; + } else { + direction = stageY > this._dragStartY ? -1 : 1; + } + + // We default to scroll a full page size; both the first + // and the last page may be smaller though, so we need to + // adjust difference in those cases. + let difference = direction * this._scrollAdjustment.page_size; + if (this._dragStartValue + difference > maxValue) + difference = maxValue - this._dragStartValue; + else if (this._dragStartValue + difference < minValue) + difference = minValue - this._dragStartValue; + + // If the user has moved more than half the scroll + // difference, we want to "settle" to the new value + // even if the user stops dragging rather "throws" by + // releasing during the drag. + let distance = this._dragStartValue - this._scrollAdjustment.value; + let noStop = Math.abs(distance / difference) > 0.5; + + // We detect if the user is stopped by comparing the + // timestamp of the button release with the timestamp of + // the last motion. Experimentally, a difference of 0 or 1 + // millisecond indicates that the mouse is in motion, a + // larger difference indicates that the mouse is stopped. + if ((this._lastMotionTime > 0 && + this._lastMotionTime > event.get_time() - 2) || + noStop) { + if (this._dragStartValue + difference >= minValue && + this._dragStartValue + difference <= maxValue) + newValue += difference; + } + + // See if the user has moved the mouse enough to trigger + // a drag + let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold; + if (Math.abs(stageX - this._dragStartX) < threshold && + Math.abs(stageY - this._dragStartY) < threshold) { + // no motion? It's a click! + this.emit('swipe-scroll-end', SwipeScrollResult.CLICK); + } else { + let result; + + if (newValue == this._dragStartValue) + result = SwipeScrollResult.CANCEL; + else + result = SwipeScrollResult.SWIPE; + + // The event capture handler is disconnected + // while scrolling to the final position, so + // to avoid undesired prelights we raise + // the cover pane. + this._coverPane.raise_top(); + this._coverPane.show(); + + Tweener.addTween(this._scrollAdjustment, + { value: newValue, + time: ANIMATION_TIME, + transition: 'easeOutQuad', + onCompleteScope: this, + onComplete: function() { + this._coverPane.hide(); + this.emit('swipe-scroll-end', + result); + } + }); + } + + global.stage.disconnect(this._capturedEventId); + this._capturedEventId = 0; + + return true; + + case Clutter.EventType.MOTION: + [stageX, stageY] = event.get_coords(); + let dx = this._dragX - stageX; + let dy = this._dragY - stageY; + let primary = global.get_primary_monitor(); + + if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) { + if (St.Widget.get_default_direction() == St.TextDirection.RTL) + this._scrollAdjustment.value -= (dx / primary.width) * this._scrollAdjustment.page_size; + else + this._scrollAdjustment.value += (dx / primary.width) * this._scrollAdjustment.page_size; + } else { + this._scrollAdjustment.value += (dy / primary.height) * this._scrollAdjustment.page_size; + } + + this._dragX = stageX; + this._dragY = stageY; + this._lastMotionTime = event.get_time(); + + return true; + + // Block enter/leave events to avoid prelights + // during swipe-scroll + case Clutter.EventType.ENTER: + case Clutter.EventType.LEAVE: + return true; + } + + return false; + }, + _getDesktopClone: function() { let windows = global.get_window_actors().filter(function(w) { return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP; @@ -335,6 +497,9 @@ Overview.prototype = { this._modal = true; this._animateVisible(); this._shown = true; + + this._buttonPressId = this._group.connect('button-press-event', + Lang.bind(this, this._onButtonPress)); }, _animateVisible: function() { @@ -402,6 +567,7 @@ Overview.prototype = { }); this._coverPane.raise_top(); + this._coverPane.show(); this.emit('showing'); }, @@ -432,6 +598,10 @@ Overview.prototype = { this._shown = false; this._syncInputMode(); + + if (this._buttonPressId > 0) + this._group.disconnect(this._buttonPressId); + this._buttonPressId = 0; }, // hideTemporarily: @@ -541,13 +711,14 @@ Overview.prototype = { }); this._coverPane.raise_top(); + this._coverPane.show(); this.emit('hiding'); }, _showDone: function() { this.animationInProgress = false; this._desktopFade.hide(); - this._coverPane.lower_bottom(); + this._coverPane.hide(); this.emit('shown'); // Handle any calls to hide* while we were showing @@ -575,7 +746,7 @@ Overview.prototype = { this.animationInProgress = false; this._hideInProgress = false; - this._coverPane.lower_bottom(); + this._coverPane.hide(); this.emit('hidden'); // Handle any calls to show* while we were hiding diff --git a/js/ui/panel.js b/js/ui/panel.js index a7bfc9f09..37b1714b3 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -44,6 +44,10 @@ const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = { if (Config.HAVE_BLUETOOTH) STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['bluetooth'] = imports.ui.status.bluetooth.Indicator; +const STANDARD_TRAY_INDICATOR_FACTORIES = [ + imports.ui.status.keyboard.ModifierIndicatorFactory +]; + // in org.gnome.desktop.interface const CLOCK_FORMAT_KEY = 'clock-format'; @@ -695,17 +699,18 @@ Panel.prototype = { /* right */ - // System status applets live in statusBox, while legacy tray icons + // On-off indicators (for keyboard leds and accessx) are in indicatorBox + // System status applets live in statusBox, and legacy tray icons // live in trayBox // The trayBox is hidden when there are no tray icons. - let statusBox = new St.BoxLayout({ name: 'statusTray' }); - let trayBox = new St.BoxLayout({ name: 'legacyTray' }); - this._trayBox = trayBox; - this._statusBox = statusBox; + this._indicatorBox = new St.BoxLayout({ name: 'indicatorBox' }); + this._trayBox = new St.BoxLayout({ name: 'legacyTray' }); + this._statusBox = new St.BoxLayout({ name: 'statusTray' }); - trayBox.hide(); - this._rightBox.add(trayBox); - this._rightBox.add(statusBox); + this._trayBox.hide(); + this._rightBox.add(this._indicatorBox); + this._rightBox.add(this._trayBox); + this._rightBox.add(this._statusBox); Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded)); Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); @@ -755,6 +760,13 @@ Panel.prototype = { }, startStatusArea: function() { + for (let i = 0; i < STANDARD_TRAY_INDICATOR_FACTORIES.length; i++) { + let factory = new STANDARD_TRAY_INDICATOR_FACTORIES[i]; + let indicators = factory.getIndicators(); + for (let j = 0; j < indicators.length; j++) + this._indicatorBox.add(indicators[j]); + } + for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) { let role = STANDARD_TRAY_ICON_ORDER[i]; let constructor = STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]; diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js index fe6c76072..45fccd6ea 100644 --- a/js/ui/searchDisplay.js +++ b/js/ui/searchDisplay.js @@ -10,6 +10,7 @@ const St = imports.gi.St; const DND = imports.ui.dnd; const IconGrid = imports.ui.iconGrid; const Main = imports.ui.main; +const Overview = imports.ui.overview; const Search = imports.ui.search; const MAX_SEARCH_RESULTS_ROWS = 2; @@ -166,7 +167,7 @@ SearchResults.prototype = { let scrollView = new St.ScrollView({ x_fill: true, y_fill: false, - vshadows: true }); + vfade: true }); scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); scrollView.add_actor(this._content); @@ -175,6 +176,15 @@ SearchResults.prototype = { expand: true, x_align: St.Align.START, y_align: St.Align.START }); + this.actor.connect('notify::mapped', Lang.bind(this, + function() { + if (!this.actor.mapped) + return; + + let adjustment = scrollView.vscroll.adjustment; + let direction = Overview.SwipeScrollDirection.VERTICAL; + Main.overview.setScrollAdjustment(adjustment, direction); + })); this._statusText = new St.Label({ style_class: 'search-statustext' }); this._content.add(this._statusText); diff --git a/js/ui/status/keyboard.js b/js/ui/status/keyboard.js index 404a766cc..3813c62de 100644 --- a/js/ui/status/keyboard.js +++ b/js/ui/status/keyboard.js @@ -42,7 +42,7 @@ LayoutMenuItem.prototype = { }; function XKBIndicator() { - this._init.apply(this, arguments); + this._init.call(this); } XKBIndicator.prototype = { @@ -75,7 +75,7 @@ XKBIndicator.prototype = { this._sync_config(); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this.menu.addAction(_("Keyboard Settings"), function() { + this.menu.addAction(_("Localization Settings"), function() { GLib.spawn_command_line_async('gnome-control-center region'); }); }, @@ -203,4 +203,53 @@ XKBIndicator.prototype = { for (let i = 0; i < this._labelActors.length; i++) this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags); } -}; \ No newline at end of file +}; + +function ModifierIndicatorFactory() { + this._init.call(this); +} + +ModifierIndicatorFactory.prototype = { + _init: function() { + this._settings = new Gio.Settings({ schema: INDICATOR_SCHEMA }); + this._settings.connect('changed::show-keyboard-leds-indicator', Lang.bind(this, this._changed)); + + this._config = Gkbd.Configuration.get(); + this._config.connect('indicators-changed', Lang.bind(this, this._changed)); + + this._scrollLock = new St.Icon({ icon_name: 'kbdled-scroll-lock', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); + this._numLock = new St.Icon({ icon_name: 'kbdled-num-lock', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); + this._capsLock = new St.Icon({ icon_name: 'kbdled-caps-lock', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); + + this._changed(); + }, + + getIndicators: function() { + return [this._scrollLock, this._numLock, this._capsLock]; + }, + + _changed: function() { + let enable = this._settings.get_boolean('show-keyboard-leds-indicator'); + + if (enable) { + if (this._config.get_scroll_lock_state()) + this._scrollLock.show(); + else + this._scrollLock.hide(); + + if (this._config.get_num_lock_state()) + this._numLock.show(); + else + this._numLock.hide(); + + if (this._config.get_caps_lock_state()) + this._capsLock.show(); + else + this._capsLock.hide(); + } else { + this._scrollLock.hide(); + this._numLock.hide(); + this._capsLock.hide(); + } + } +}; diff --git a/js/ui/workspace.js b/js/ui/workspace.js index 600249294..e44f5584d 100644 --- a/js/ui/workspace.js +++ b/js/ui/workspace.js @@ -517,20 +517,10 @@ Workspace.prototype = { // Without this the drop area will be overlapped. this._windowOverlaysGroup.set_size(0, 0); - this.actor = new Clutter.Group({ reactive: true }); + this.actor = new Clutter.Group(); this.actor._delegate = this; this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); - this.actor.connect('button-release-event', Lang.bind(this, - function(actor, event) { - // Only switch to the workspace when there's no application - // windows open. The problem is that it's too easy to miss - // an app window and get the wrong one focused. - if (this._windows.length == 0) { - this.metaWorkspace.activate(event.get_time()); - Main.overview.hide(); - } - })); // Items in _windowOverlaysGroup should not be scaled, so we don't // add them to this.actor, but to its parent whenever it changes @@ -605,6 +595,10 @@ Workspace.prototype = { return this._lookupIndex(metaWindow) >= 0; }, + isEmpty: function() { + return this._windows.length == 0; + }, + setShowOnlyWindows: function(showOnlyWindows, reposition) { this._showOnlyWindows = showOnlyWindows; this._resetCloneVisibility(); diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js index 747800391..40b7320d9 100644 --- a/js/ui/workspacesView.js +++ b/js/ui/workspacesView.js @@ -45,6 +45,8 @@ WorkspacesView.prototype = { this._spacing = node.get_length('spacing'); this._computeWorkspacePositions(); })); + this.actor.connect('notify::mapped', + Lang.bind(this, this._onMappedChanged)); this._width = width; this._height = height; @@ -55,10 +57,9 @@ WorkspacesView.prototype = { this._activeWorkspaceY = 0; // y offset of active ws while dragging this._lostWorkspaces = []; this._animating = false; // tweening - this._scrolling = false; // dragging desktop + this._scrolling = false; // swipe-scrolling this._animatingScroll = false; // programatically updating the adjustment this._inDrag = false; // dragging a window - this._lastMotionTime = -1; // used to track "stopping" while dragging workspaces let activeWorkspaceIndex = global.screen.get_active_workspace_index(); this._workspaces = workspaces; @@ -89,10 +90,6 @@ WorkspacesView.prototype = { this._scrollAdjustment.connect('notify::value', Lang.bind(this, this._onScroll)); - this._dragIndex = -1; - - this._buttonPressId = 0; - this._capturedEventId = 0; this._timeoutId = 0; this._windowSelectionAppId = null; @@ -113,6 +110,8 @@ WorkspacesView.prototype = { Lang.bind(this, this._dragBegin)); this._windowDragEndId = Main.overview.connect('window-drag-end', Lang.bind(this, this._dragEnd)); + this._swipeScrollBeginId = 0; + this._swipeScrollEndId = 0; }, _lookupWorkspaceForMetaWindow: function (metaWindow) { @@ -290,8 +289,6 @@ WorkspacesView.prototype = { if (this._inDrag) scale *= WORKSPACE_DRAGGING_SCALE; - this._setWorkspaceDraggable(active, true); - let _width = this._workspaces[0].actor.width * scale; let _height = this._workspaces[0].actor.height * scale; @@ -323,128 +320,6 @@ WorkspacesView.prototype = { this._updateScrollAdjustment(active, showAnimation); }, - // _setWorkspaceDraggable: - // @index: workspace index - // @draggable: whether workspace @index should be draggable - // - // If @draggable is %true, set up workspace @index to allow switching - // workspaces by dragging the desktop - if a draggable workspace has - // been set up before, it will be reset before the new one is made - // draggable. - // If @draggable is %false, workspace @index is reset to no longer allow - // dragging. - _setWorkspaceDraggable: function(index, draggable) { - if (index < 0 || index >= global.n_workspaces) - return; - - let dragActor = this._workspaces[index].actor; - - if (draggable) { - this._workspaces[index].actor.reactive = true; - - // reset old draggable workspace - if (this._dragIndex > -1) - this._setWorkspaceDraggable(this._dragIndex, false); - - this._dragIndex = index; - this._buttonPressId = dragActor.connect('button-press-event', - Lang.bind(this, this._onButtonPress)); - } else { - this._dragIndex = -1; - - if (this._buttonPressId > 0) { - if (dragActor.get_stage()) - dragActor.disconnect(this._buttonPressId); - this._buttonPressId = 0; - } - - if (this._capturedEventId > 0) { - global.stage.disconnect(this._capturedEventId); - this._capturedEventId = 0; - } - } - }, - - // start dragging the active workspace - _onButtonPress: function(actor, event) { - if (actor != event.get_source()) - return; - - if (this._dragIndex == -1) - return; - - let [stageX, stageY] = event.get_coords(); - this._dragStartX = this._dragX = stageX; - this._scrolling = true; - this._capturedEventId = global.stage.connect('captured-event', - Lang.bind(this, this._onCapturedEvent)); - }, - - // handle captured events while dragging a workspace - _onCapturedEvent: function(actor, event) { - let active = global.screen.get_active_workspace_index(); - let stageX, stageY; - - switch (event.type()) { - case Clutter.EventType.BUTTON_RELEASE: - this._scrolling = false; - - [stageX, stageY] = event.get_coords(); - - // default to snapping back to the original workspace - let activate = this._dragIndex; - let last = global.screen.n_workspaces - 1; - - // If the user has moved more than half a workspace, we want to "settle" - // to the new workspace even if the user stops dragging rather "throws" - // by releasing during the drag. - let noStop = Math.abs(activate - this._scrollAdjustment.value) > 0.5; - - let difference = stageX > this._dragStartX ? -1 : 1; - if (St.Widget.get_default_direction() == St.TextDirection.RTL) - difference *= -1; - - // We detect if the user is stopped by comparing the timestamp of the button - // release with the timestamp of the last motion. Experimentally, a difference - // of 0 or 1 millisecond indicates that the mouse is in motion, a larger - // difference indicates that the mouse is stopped. - if ((this._lastMotionTime > 0 && this._lastMotionTime > event.get_time() - 2) || noStop) { - if (activate + difference >= 0 && - activate + difference <= last) - activate += difference; - } - - if (activate != active) { - let workspace = this._workspaces[activate].metaWorkspace; - workspace.activate(global.get_current_time()); - } else { - this._scrollToActive(true); - } - - if (stageX == this._dragStartX) - // no motion? It's a click! - return false; - - return true; - - case Clutter.EventType.MOTION: - [stageX, stageY] = event.get_coords(); - let dx = this._dragX - stageX; - let primary = global.get_primary_monitor(); - - if (St.Widget.get_default_direction() == St.TextDirection.RTL) - this._scrollAdjustment.value -= (dx / primary.width); - else - this._scrollAdjustment.value += (dx / primary.width); - this._dragX = stageX; - this._lastMotionTime = event.get_time(); - - return true; - } - - return false; - }, - // Update workspace actors parameters to the values calculated in // _computeWorkspacePositions() // @showAnimation: iff %true, transition between states @@ -454,7 +329,6 @@ WorkspacesView.prototype = { let targetWorkspaceCurrentX = this._workspaces[active].x; let dx = targetWorkspaceNewX - targetWorkspaceCurrentX; - this._setWorkspaceDraggable(active, true); this._animating = showAnimation; for (let w = 0; w < this._workspaces.length; w++) { @@ -606,7 +480,6 @@ WorkspacesView.prototype = { global.window_manager.disconnect(this._switchWorkspaceNotifyId); global.screen.disconnect(this._restackedNotifyId); - this._setWorkspaceDraggable(this._dragIndex, false); if (this._timeoutId) { Mainloop.source_remove(this._timeoutId); this._timeoutId = 0; @@ -629,6 +502,21 @@ WorkspacesView.prototype = { } }, + _onMappedChanged: function() { + if (this.actor.mapped) { + let direction = Overview.SwipeScrollDirection.HORIZONTAL; + Main.overview.setScrollAdjustment(this._scrollAdjustment, + direction); + this._swipeScrollBeginId = Main.overview.connect('swipe-scroll-begin', + Lang.bind(this, this._swipeScrollBegin)); + this._swipeScrollEndId = Main.overview.connect('swipe-scroll-end', + Lang.bind(this, this._swipeScrollEnd)); + } else { + Main.overview.disconnect(this._swipeScrollBeginId); + Main.overview.disconnect(this._swipeScrollEndId); + } + }, + _dragBegin: function() { if (this._scrolling) return; @@ -736,6 +624,37 @@ WorkspacesView.prototype = { this._workspaces[i].setReservedSlot(null); }, + _swipeScrollBegin: function() { + this._scrolling = true; + }, + + _swipeScrollEnd: function(overview, result) { + this._scrolling = false; + + if (result == Overview.SwipeScrollResult.CLICK) { + let [x, y, mod] = global.get_pointer(); + let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, + x, y); + + // Only switch to the workspace when there's no application + // windows open. The problem is that it's too easy to miss + // an app window and get the wrong one focused. + let active = global.screen.get_active_workspace_index(); + if (this._workspaces[active].isEmpty() && + this.actor.contains(actor)) + Main.overview.hide(); + } + + if (result == Overview.SwipeScrollResult.SWIPE) + // The active workspace has changed; while swipe-scrolling + // has already taken care of the positioning, the cached + // positions need to be updated + this._computeWorkspacePositions(); + + // Make sure title captions etc are shown as necessary + this._updateVisibility(); + }, + // sync the workspaces' positions to the value of the scroll adjustment // and change the active workspace if appropriate _onScroll: function(adj) { diff --git a/po/ar.po b/po/ar.po index f6f6902b9..9c7b69342 100644 --- a/po/ar.po +++ b/po/ar.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: HEAD\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-19 22:47+0200\n" -"PO-Revision-Date: 2011-01-19 22:47+0300\n" +"POT-Creation-Date: 2011-01-23 13:29+0200\n" +"PO-Revision-Date: 2011-01-23 13:29+0300\n" "Last-Translator: Khaled Hosny \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" @@ -70,7 +70,8 @@ msgstr "" msgid "List of desktop file IDs for favorite applications" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:11 +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#, no-c-format msgid "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -79,45 +80,47 @@ msgid "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." -msgstr "" - -#: ../data/org.gnome.shell.gschema.xml.in.h:12 -msgid "Show date in clock" +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." msgstr "" #: ../data/org.gnome.shell.gschema.xml.in.h:13 -msgid "Show the week date in the calendar" +msgid "Show date in clock" msgstr "" #: ../data/org.gnome.shell.gschema.xml.in.h:14 -msgid "Show time with seconds" +msgid "Show the week date in the calendar" msgstr "" #: ../data/org.gnome.shell.gschema.xml.in.h:15 +msgid "Show time with seconds" +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:16 msgid "" "The applications corresponding to these identifiers will be displayed in the " "favorites area." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:16 +#: ../data/org.gnome.shell.gschema.xml.in.h:17 msgid "" "The filename for recorded screencasts will be a unique filename based on the " "current date, and use this extension. It should be changed when recording to " "a different container format." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:17 +#: ../data/org.gnome.shell.gschema.xml.in.h:18 msgid "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:18 +#: ../data/org.gnome.shell.gschema.xml.in.h:19 msgid "The gstreamer pipeline used to encode the screencast" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:19 +#: ../data/org.gnome.shell.gschema.xml.in.h:20 msgid "" "The shell normally monitors active applications in order to present the most " "used ones (e.g. in launchers). While this data will be kept private, you may " @@ -125,15 +128,15 @@ msgid "" "remove already saved data." msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:20 +#: ../data/org.gnome.shell.gschema.xml.in.h:21 msgid "Uuids of extensions to disable" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:21 +#: ../data/org.gnome.shell.gschema.xml.in.h:22 msgid "Whether to collect stats about applications usage" msgstr "" -#: ../data/org.gnome.shell.gschema.xml.in.h:22 +#: ../data/org.gnome.shell.gschema.xml.in.h:23 msgid "disabled OpenSearch providers" msgstr "" @@ -436,54 +439,54 @@ msgid "Applications" msgstr "التطبيقات" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:479 +#: ../js/ui/panel.js:483 #, c-format msgid "Quit %s" msgstr "أغلق %s" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:564 +#: ../js/ui/panel.js:568 msgid "%a %b %e, %R:%S" msgstr "%A %e %B، %R:%S" -#: ../js/ui/panel.js:565 +#: ../js/ui/panel.js:569 msgid "%a %b %e, %R" msgstr "%A %e %B، %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:569 +#: ../js/ui/panel.js:573 msgid "%a %R:%S" msgstr "%A %R:%S" -#: ../js/ui/panel.js:570 +#: ../js/ui/panel.js:574 msgid "%a %R" msgstr "%A %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:581 msgid "%a %b %e, %l:%M:%S %p" msgstr "%A %e %B، %l:%M:%S %p" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:582 msgid "%a %b %e, %l:%M %p" msgstr "%A %e %B، %l:%M %p" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %l:%M:%S %p" msgstr "%A %l:%M:%S %p" -#: ../js/ui/panel.js:583 +#: ../js/ui/panel.js:587 msgid "%a %l:%M %p" msgstr "%A %Ol:%OM %p" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:728 +#: ../js/ui/panel.js:732 msgid "Activities" msgstr "الأنشطة" @@ -638,7 +641,7 @@ msgstr "عطل أثناء تصفّح الجهاز" msgid "The requested device cannot be browsed, error is '%s'" msgstr "تعذّر تصفح الجهاز، رسالة العطل '%s'" -#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 msgid "Keyboard Settings" msgstr "إعدادات لوحة المفاتيح" @@ -713,6 +716,10 @@ msgstr "من فضلك أدخل الرقم المذكور على الجهاز." msgid "OK" msgstr "حسنا" +#: ../js/ui/status/keyboard.js:78 +msgid "Localization Settings" +msgstr "إعدادات اللغة" + #: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "إعدادات الطاقة" diff --git a/po/es.po b/po/es.po index 0fb03cdb4..c45ed73a8 100644 --- a/po/es.po +++ b/po/es.po @@ -1,17 +1,17 @@ # Spanish translation of gnome-shell. # Copyright (C) 2009 gnome-shell's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-shell package. -# Jorge González , 2009, 2010. # Daniel Mustieles , 2010, 2011. +# Jorge González , 2009, 2010, 2011. # msgid "" msgstr "" "Project-Id-Version: gnome-shell.master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2011-01-17 21:45+0000\n" -"PO-Revision-Date: 2011-01-19 16:17+0100\n" -"Last-Translator: Daniel Mustieles \n" +"POT-Creation-Date: 2011-01-21 18:46+0000\n" +"PO-Revision-Date: 2011-01-22 16:34+0100\n" +"Last-Translator: Jorge González \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -76,7 +76,18 @@ msgstr "Si es cierta muestra la fecha de semana ISO en el calendario." msgid "List of desktop file IDs for favorite applications" msgstr "Lista de ID de archivos de escritorio para las aplicaciones favoritas" -#: ../data/org.gnome.shell.gschema.xml.in.h:11 +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#, no-c-format +#| msgid "" +#| "Sets the GStreamer pipeline used to encode recordings. It follows the " +#| "syntax used for gst-launch. The pipeline should have an unconnected sink " +#| "pad where the recorded video is recorded. It will normally have a " +#| "unconnected source pad; output from that pad will be written into the " +#| "output file. However the pipeline can also take care of its own output - " +#| "this might be used to send the output to an icecast server via shout2send " +#| "or similar. When unset or set to an empty value, the default pipeline " +#| "will be used. This is currently 'videorate ! theoraenc ! oggmux' and " +#| "records to Ogg Theora." msgid "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -85,32 +96,35 @@ msgid "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." msgstr "" "Establece la tubería GStreamer usada para codificar grabaciones. Sigue la " -"sintaxis usada para gst-launch. La tubería debería tener un sumidero («sink») " -"de ensamblaje/sesensamblaje donde el vídeo que se está grabando se graba. " -"Generalmente tendrá un origen de ensamblado/desensamblado; la salida de ese " -"punto se escibirá en el archivo de salida. No obstante la tubería también " -"puede tomar parte en su propia salida; esto se puede usar para enviar la " -"salida a un servidor «icecast» a través de shout2send o similar. Cuando no " -"está establecido o lo está a un valor vacío, se usará la tubería " -"predeterminada. Actualmente es «videorate ! theoraenc ! oggmux» y greba en " -"Ogg Theora." +"sintaxis usada para gst-launch. La tubería debería tener un sumidero " +"(«sink») de ensamblaje/sesensamblaje donde el vídeo que se está grabando se " +"graba. Generalmente tendrá un origen de ensamblado/desensamblado; la salida " +"de ese punto se escibirá en el archivo de salida. No obstante la tubería " +"también puede tomar parte en su propia salida; esto se puede usar para " +"enviar la salida a un servidor «icecast» a través de shout2send o similar. " +"Cuando no está establecido o lo está a un valor vacío, se usará la tubería " +"predeterminada. Actualmente es «videorate ! vp8enc quality=10 speed=2 " +"threads=%T ! queue ! webmmux» y greba en WEBM usando el códec VP8. Se usa %T " +"como suposición para el número de hilos óptimos en el sistema." -#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#: ../data/org.gnome.shell.gschema.xml.in.h:13 msgid "Show date in clock" msgstr "Mostrar la fecha en el reloj" -#: ../data/org.gnome.shell.gschema.xml.in.h:13 +#: ../data/org.gnome.shell.gschema.xml.in.h:14 msgid "Show the week date in the calendar" msgstr "Mostrar la fecha de la semana en el calendario" -#: ../data/org.gnome.shell.gschema.xml.in.h:14 +#: ../data/org.gnome.shell.gschema.xml.in.h:15 msgid "Show time with seconds" msgstr "Mostrar la hora con segundos" -#: ../data/org.gnome.shell.gschema.xml.in.h:15 +#: ../data/org.gnome.shell.gschema.xml.in.h:16 msgid "" "The applications corresponding to these identifiers will be displayed in the " "favorites area." @@ -118,7 +132,7 @@ msgstr "" "Las aplicaciones correspondientes con esos identificadores se mostrarán en " "el área de favoritos." -#: ../data/org.gnome.shell.gschema.xml.in.h:16 +#: ../data/org.gnome.shell.gschema.xml.in.h:17 msgid "" "The filename for recorded screencasts will be a unique filename based on the " "current date, and use this extension. It should be changed when recording to " @@ -128,7 +142,7 @@ msgstr "" "basado en la fecha actual y usará esta extensión. Se debería cambiar al " "grabar en otro formato contenedor diferente." -#: ../data/org.gnome.shell.gschema.xml.in.h:17 +#: ../data/org.gnome.shell.gschema.xml.in.h:18 msgid "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." @@ -136,11 +150,11 @@ msgstr "" "La tasa de fotogramas de la grabación resultante grabada por el grabador de " "«screencast» de GNOME Shell, en fotogramas por segundo." -#: ../data/org.gnome.shell.gschema.xml.in.h:18 +#: ../data/org.gnome.shell.gschema.xml.in.h:19 msgid "The gstreamer pipeline used to encode the screencast" msgstr "La tubería de gstreamer usada para codificar el «screencast»" -#: ../data/org.gnome.shell.gschema.xml.in.h:19 +#: ../data/org.gnome.shell.gschema.xml.in.h:20 msgid "" "The shell normally monitors active applications in order to present the most " "used ones (e.g. in launchers). While this data will be kept private, you may " @@ -152,16 +166,16 @@ msgstr "" "mantienen de forma privada, puede querer desactivarlo por razones de " "privacidad. Note que haciéndolo no eliminará los datos ya guardados." -#: ../data/org.gnome.shell.gschema.xml.in.h:20 +#: ../data/org.gnome.shell.gschema.xml.in.h:21 msgid "Uuids of extensions to disable" msgstr "Uuid de las extensiones que desactivar" -#: ../data/org.gnome.shell.gschema.xml.in.h:21 +#: ../data/org.gnome.shell.gschema.xml.in.h:22 msgid "Whether to collect stats about applications usage" msgstr "" "Indica si se deben recolectar estadísticas acerca del uso de las aplicaciones" -#: ../data/org.gnome.shell.gschema.xml.in.h:22 +#: ../data/org.gnome.shell.gschema.xml.in.h:23 msgid "disabled OpenSearch providers" msgstr "proveedores OpenSearch desactivados" @@ -482,7 +496,7 @@ msgstr "Ver fuente" msgid "Web Page" msgstr "Página web" -#: ../js/ui/messageTray.js:1748 +#: ../js/ui/messageTray.js:1765 msgid "System Information" msgstr "Información del sistema" @@ -499,54 +513,54 @@ msgid "Applications" msgstr "Aplicaciones" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:479 +#: ../js/ui/panel.js:483 #, c-format msgid "Quit %s" msgstr "Salir de %s" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:564 +#: ../js/ui/panel.js:568 msgid "%a %b %e, %R:%S" msgstr "%a %e de %b, %R:%S" -#: ../js/ui/panel.js:565 +#: ../js/ui/panel.js:569 msgid "%a %b %e, %R" msgstr "%a %e de %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:569 +#: ../js/ui/panel.js:573 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:570 +#: ../js/ui/panel.js:574 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:581 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e de %b, %H:%M:%S" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:582 msgid "%a %b %e, %l:%M %p" msgstr "%a %e de %b, %H:%M" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %l:%M:%S %p" msgstr "%a %H:%M:%S" -#: ../js/ui/panel.js:583 +#: ../js/ui/panel.js:587 msgid "%a %l:%M %p" msgstr "%a %H:%M" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:728 +#: ../js/ui/panel.js:732 msgid "Activities" msgstr "Actividades" @@ -701,7 +715,7 @@ msgstr "Error al examinar el dispositivo" msgid "The requested device cannot be browsed, error is '%s'" msgstr "No se puede examinar el dispositivo solicitado, el error es «%s»" -#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 msgid "Keyboard Settings" msgstr "Configuración del teclado" @@ -776,6 +790,11 @@ msgstr "Introduzca el PIN mencionado en el dispositivo." msgid "OK" msgstr "Aceptar" +#: ../js/ui/status/keyboard.js:78 +#| msgid "Sound Settings" +msgid "Localization Settings" +msgstr "Configuración regional" + #: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "Configuración de energía" @@ -1015,8 +1034,8 @@ msgstr "%1$s: %2$s" #~ "If true and format is either \"12-hour\" or \"24-hour\", display seconds " #~ "in time." #~ msgstr "" -#~ "Si es cierta y el formato es «12-horas» o «24-horas», muestra los segundos " -#~ "en la hora." +#~ "Si es cierta y el formato es «12-horas» o «24-horas», muestra los " +#~ "segundos en la hora." #~ msgid "" #~ "This key specifies the format used by the panel clock when the format key " @@ -1033,18 +1052,19 @@ msgstr "%1$s: %2$s" #~ msgid "" #~ "This key specifies the hour format used by the panel clock. Possible " #~ "values are \"12-hour\", \"24-hour\", \"unix\" and \"custom\". If set to " -#~ "\"unix\", the clock will display time in seconds since Epoch, i.e. 1970-" -#~ "01-01. If set to \"custom\", the clock will display time according to the " -#~ "format specified in the custom_format key. Note that if set to either " -#~ "\"unix\" or \"custom\", the show_date and show_seconds keys are ignored." +#~ "\"unix\", the clock will display time in seconds since Epoch, i.e. " +#~ "1970-01-01. If set to \"custom\", the clock will display time according " +#~ "to the format specified in the custom_format key. Note that if set to " +#~ "either \"unix\" or \"custom\", the show_date and show_seconds keys are " +#~ "ignored." #~ msgstr "" #~ "Esta clave especifica el formato de la hora especificado por el reloj del " -#~ "panel. Los valores posibles son «12-hour» (12 horas), «24-hour» (24 horas), " -#~ "«unix» y «custom» (personalizado).Si se establece a «unix» el reloj mostrará " -#~ "la hora en segundos desde la época (1 de enero de 1970). Si se establece " -#~ "a «custom» el reloj mostrará la hora según el formato especificado en la " -#~ "clave «custom_format». Note que si se establece a «unix» o «custom» se " -#~ "ignoran las claves «show_date» y «show_seconds»." +#~ "panel. Los valores posibles son «12-hour» (12 horas), «24-hour» (24 " +#~ "horas), «unix» y «custom» (personalizado).Si se establece a «unix» el " +#~ "reloj mostrará la hora en segundos desde la época (1 de enero de 1970). " +#~ "Si se establece a «custom» el reloj mostrará la hora según el formato " +#~ "especificado en la clave «custom_format». Note que si se establece a " +#~ "«unix» o «custom» se ignoran las claves «show_date» y «show_seconds»." #~ msgid "Clock Format" #~ msgstr "Formato del reloj" diff --git a/po/he.po b/po/he.po index fda1af7c1..e87ad6e7a 100644 --- a/po/he.po +++ b/po/he.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-18 13:02+0200\n" -"PO-Revision-Date: 2011-01-18 13:05+0200\n" +"POT-Creation-Date: 2011-01-23 02:42+0200\n" +"PO-Revision-Date: 2011-01-23 02:43+0200\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" @@ -73,7 +73,8 @@ msgstr "If true, display the ISO week date in the calendar." msgid "List of desktop file IDs for favorite applications" msgstr "List of desktop file IDs for favorite applications" -#: ../data/org.gnome.shell.gschema.xml.in.h:11 +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#, no-c-format msgid "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -82,7 +83,9 @@ msgid "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." msgstr "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -91,51 +94,53 @@ msgstr "" "pipeline can also take care of its own output - this might be used to send " "the output to an icecast server via shout2send or similar. When unset or set " "to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and " +"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess " +"at the optimal thread count on the system." -#: ../data/org.gnome.shell.gschema.xml.in.h:12 +#: ../data/org.gnome.shell.gschema.xml.in.h:13 msgid "Show date in clock" msgstr "Show date in clock" -#: ../data/org.gnome.shell.gschema.xml.in.h:13 +#: ../data/org.gnome.shell.gschema.xml.in.h:14 msgid "Show the week date in the calendar" msgstr "Show the week date in the calendar" -#: ../data/org.gnome.shell.gschema.xml.in.h:14 +#: ../data/org.gnome.shell.gschema.xml.in.h:15 msgid "Show time with seconds" msgstr "Show time with seconds" -#: ../data/org.gnome.shell.gschema.xml.in.h:15 -msgid "" -"The applications corresponding to these identifiers will be displayed in the " -"favorites area." -msgstr "" -"The applications corresponding to these identifiers will be displayed in the " -"favorites area." - #: ../data/org.gnome.shell.gschema.xml.in.h:16 msgid "" -"The filename for recorded screencasts will be a unique filename based on the " -"current date, and use this extension. It should be changed when recording to " -"a different container format." +"The applications corresponding to these identifiers will be displayed in the " +"favorites area." msgstr "" -"The filename for recorded screencasts will be a unique filename based on the " -"current date, and use this extension. It should be changed when recording to " -"a different container format." +"The applications corresponding to these identifiers will be displayed in the " +"favorites area." #: ../data/org.gnome.shell.gschema.xml.in.h:17 msgid "" +"The filename for recorded screencasts will be a unique filename based on the " +"current date, and use this extension. It should be changed when recording to " +"a different container format." +msgstr "" +"The filename for recorded screencasts will be a unique filename based on the " +"current date, and use this extension. It should be changed when recording to " +"a different container format." + +#: ../data/org.gnome.shell.gschema.xml.in.h:18 +msgid "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." msgstr "" "The framerate of the resulting screencast recordered by GNOME Shell's " "screencast recorder in frames-per-second." -#: ../data/org.gnome.shell.gschema.xml.in.h:18 +#: ../data/org.gnome.shell.gschema.xml.in.h:19 msgid "The gstreamer pipeline used to encode the screencast" msgstr "The gstreamer pipeline used to encode the screencast" -#: ../data/org.gnome.shell.gschema.xml.in.h:19 +#: ../data/org.gnome.shell.gschema.xml.in.h:20 msgid "" "The shell normally monitors active applications in order to present the most " "used ones (e.g. in launchers). While this data will be kept private, you may " @@ -147,15 +152,15 @@ msgstr "" "want to disable this for privacy reasons. Please note that doing so won't " "remove already saved data." -#: ../data/org.gnome.shell.gschema.xml.in.h:20 +#: ../data/org.gnome.shell.gschema.xml.in.h:21 msgid "Uuids of extensions to disable" msgstr "Uuids of extensions to disable" -#: ../data/org.gnome.shell.gschema.xml.in.h:21 +#: ../data/org.gnome.shell.gschema.xml.in.h:22 msgid "Whether to collect stats about applications usage" msgstr "Whether to collect stats about applications usage" -#: ../data/org.gnome.shell.gschema.xml.in.h:22 +#: ../data/org.gnome.shell.gschema.xml.in.h:23 msgid "disabled OpenSearch providers" msgstr "disabled OpenSearch providers" @@ -471,7 +476,7 @@ msgstr "צפייה במקור" msgid "Web Page" msgstr "דף אינטרנט" -#: ../js/ui/messageTray.js:1748 +#: ../js/ui/messageTray.js:1765 msgid "System Information" msgstr "פרטי המערכת" @@ -488,54 +493,54 @@ msgid "Applications" msgstr "יישומים" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:479 +#: ../js/ui/panel.js:483 #, c-format msgid "Quit %s" msgstr "יציאה מ־%s" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:564 +#: ../js/ui/panel.js:568 msgid "%a %b %e, %R:%S" msgstr "%a %b %e, %R:%S" -#: ../js/ui/panel.js:565 +#: ../js/ui/panel.js:569 msgid "%a %b %e, %R" msgstr "%a %b %e, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:569 +#: ../js/ui/panel.js:573 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:570 +#: ../js/ui/panel.js:574 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:581 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %b %e, %l:%M:%S %p" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:582 msgid "%a %b %e, %l:%M %p" msgstr "%a %b %e, %l:%M %p" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %l:%M:%S %p" msgstr "%a %l:%M:%S %p" -#: ../js/ui/panel.js:583 +#: ../js/ui/panel.js:587 msgid "%a %l:%M %p" msgstr "%a %l:%M %p" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:728 +#: ../js/ui/panel.js:732 msgid "Activities" msgstr "פעילויות" @@ -690,7 +695,7 @@ msgstr "שגיאה בעיון בהתקן" msgid "The requested device cannot be browsed, error is '%s'" msgstr "לא ניתן לעיין בהתקן הנבחר, השגיאה היא '%s'" -#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 msgid "Keyboard Settings" msgstr "הגדרות מקלדת" @@ -765,6 +770,10 @@ msgstr "נא להזין את קוד ה־PIN המוזכר בהתקן." msgid "OK" msgstr "אישור" +#: ../js/ui/status/keyboard.js:78 +msgid "Localization Settings" +msgstr "הגדרות אוזריות" + #: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "הגדרות צריכת החשמל" diff --git a/po/it.po b/po/it.po index 4b6f085ed..fca96959f 100644 --- a/po/it.po +++ b/po/it.po @@ -8,14 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: gnome-shell\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-01-15 01:10+0100\n" +"POT-Creation-Date: 2011-01-21 01:16+0100\n" "PO-Revision-Date: 2011-01-15 01:19+0100\n" "Last-Translator: Luca Ferretti \n" "Language-Team: Italian \n" +"Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../data/gnome-shell.desktop.in.in.h:1 @@ -161,6 +161,10 @@ msgid "Whether to collect stats about applications usage" msgstr "" "Indica se raccogliere statistiche riguardo l'utilizzo delle applicazioni" +#: ../data/org.gnome.shell.gschema.xml.in.h:22 +msgid "disabled OpenSearch providers" +msgstr "" + #: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:1 msgid "Clip the crosshairs at the center" msgstr "" @@ -296,7 +300,7 @@ msgstr "Impossibile analizzare il comando:" msgid "No such application" msgstr "Applicazione inesistente" -#: ../js/misc/util.js:143 ../js/ui/runDialog.js:364 +#: ../js/misc/util.js:143 ../js/ui/runDialog.js:351 #, c-format msgid "Execution of '%s' failed:" msgstr "Esecuzione di «%s» non riuscita:" @@ -344,7 +348,7 @@ msgstr "Rimuovi" #: ../js/ui/docDisplay.js:18 msgid "RECENT ITEMS" -msgstr "Elementi recenti" +msgstr "ELEMENTI RECENTI" #: ../js/ui/endSessionDialog.js:63 #, c-format @@ -358,7 +362,9 @@ msgstr "Termina sessione" # oddio... abbandonare il sistema sembra la nave che affonda... (LF) #: ../js/ui/endSessionDialog.js:65 msgid "Click Log Out to quit these applications and log out of the system." -msgstr "Fare clic su «Termina sessione» per chiudere queste applicazioni e abbandonare il sistema." +msgstr "" +"Fare clic su «Termina sessione» per chiudere queste applicazioni e " +"abbandonare il sistema." #: ../js/ui/endSessionDialog.js:66 #, c-format @@ -381,7 +387,9 @@ msgstr "Arresta" # usato un termine diverso, magari si capisce meglio (LF) #: ../js/ui/endSessionDialog.js:75 msgid "Click Shut Down to quit these applications and shut down the system." -msgstr "Fare clic su «Arresta» per chiudere queste applicazioni e spegnere il sistema." +msgstr "" +"Fare clic su «Arresta» per chiudere queste applicazioni e spegnere il " +"sistema." #: ../js/ui/endSessionDialog.js:76 #, c-format @@ -398,7 +406,9 @@ msgstr "Riavvia" #: ../js/ui/endSessionDialog.js:85 msgid "Click Restart to quit these applications and restart the system." -msgstr "Fare clic su «Riavvia» per chiudere queste applicazioni e riavviare il sistema." +msgstr "" +"Fare clic su «Riavvia» per chiudere queste applicazioni e riavviare il " +"sistema." #: ../js/ui/endSessionDialog.js:86 #, c-format @@ -413,7 +423,7 @@ msgstr "Riavvio del sistema." msgid "Confirm" msgstr "Conferma" -#: ../js/ui/endSessionDialog.js:400 ../js/ui/status/bluetooth.js:469 +#: ../js/ui/endSessionDialog.js:400 ../js/ui/status/bluetooth.js:470 msgid "Cancel" msgstr "Annulla" @@ -449,7 +459,7 @@ msgstr "Visualizza sorgente" msgid "Web Page" msgstr "Pagina web" -#: ../js/ui/messageTray.js:1748 +#: ../js/ui/messageTray.js:1765 msgid "System Information" msgstr "Informazione di sistema" @@ -519,22 +529,22 @@ msgid "Activities" msgstr "Attività" # (ndt) libera, ma unmount non si può proprio vedere... -#: ../js/ui/placeDisplay.js:112 +#: ../js/ui/placeDisplay.js:106 #, c-format msgid "Failed to unmount '%s'" msgstr "Impossibile scollegare «%s»" -#: ../js/ui/placeDisplay.js:115 +#: ../js/ui/placeDisplay.js:109 msgid "Retry" msgstr "Riprova" -#: ../js/ui/placeDisplay.js:160 +#: ../js/ui/placeDisplay.js:150 msgid "Connect to..." msgstr "Connetti a..." -#: ../js/ui/placeDisplay.js:559 +#: ../js/ui/placeDisplay.js:386 msgid "PLACES & DEVICES" -msgstr "Risorse e dispositivi" +msgstr "RISORSE E DISPOSITIVI" #. Translators: this MUST be either "toggle-switch-us" #. (for toggle switches containing the English words @@ -545,7 +555,7 @@ msgstr "Risorse e dispositivi" msgid "toggle-switch-us" msgstr "toggle-switch-intl" -#: ../js/ui/runDialog.js:222 +#: ../js/ui/runDialog.js:209 msgid "Please enter a command:" msgstr "Inserire un comando:" @@ -629,7 +639,7 @@ msgstr "Contrasto elevato" msgid "Large Text" msgstr "Caratteri grandi" -#: ../js/ui/status/bluetooth.js:42 ../js/ui/status/bluetooth.js:240 +#: ../js/ui/status/bluetooth.js:42 ../js/ui/status/bluetooth.js:241 msgid "Bluetooth" msgstr "Bluetooth" @@ -646,105 +656,105 @@ msgstr "Invia file al dispositivo..." msgid "Setup a New Device..." msgstr "Imposta un nuovo dispositivo..." -#: ../js/ui/status/bluetooth.js:94 +#: ../js/ui/status/bluetooth.js:95 msgid "Bluetooth Settings" msgstr "Impostazioni Bluetooth" # indica lo stato del device BT, per esempio gli auricolari # credo sia meglio l'aggettivo che il sostantivo -#: ../js/ui/status/bluetooth.js:191 +#: ../js/ui/status/bluetooth.js:192 msgid "Connection" msgstr "Collegato" -#: ../js/ui/status/bluetooth.js:227 +#: ../js/ui/status/bluetooth.js:228 msgid "Send Files..." msgstr "Invia file..." -#: ../js/ui/status/bluetooth.js:232 +#: ../js/ui/status/bluetooth.js:233 msgid "Browse Files..." msgstr "Esplora file..." -#: ../js/ui/status/bluetooth.js:241 +#: ../js/ui/status/bluetooth.js:242 msgid "Error browsing device" msgstr "Errore nell'esplorare il dispositivo" -#: ../js/ui/status/bluetooth.js:242 +#: ../js/ui/status/bluetooth.js:243 #, c-format msgid "The requested device cannot be browsed, error is '%s'" msgstr "Non è possibile esplorare il dispositivo richiesto, l'errore è «%s»" -#: ../js/ui/status/bluetooth.js:250 ../js/ui/status/keyboard.js:78 +#: ../js/ui/status/bluetooth.js:251 ../js/ui/status/keyboard.js:78 msgid "Keyboard Settings" msgstr "Impostazioni tastiera" -#: ../js/ui/status/bluetooth.js:255 +#: ../js/ui/status/bluetooth.js:256 msgid "Mouse Settings" msgstr "Impostazioni mouse" -#: ../js/ui/status/bluetooth.js:262 ../js/ui/status/volume.js:63 +#: ../js/ui/status/bluetooth.js:263 ../js/ui/status/volume.js:63 msgid "Sound Settings" msgstr "Impostazioni audio" -#: ../js/ui/status/bluetooth.js:336 ../js/ui/status/bluetooth.js:370 -#: ../js/ui/status/bluetooth.js:410 ../js/ui/status/bluetooth.js:443 +#: ../js/ui/status/bluetooth.js:337 ../js/ui/status/bluetooth.js:371 +#: ../js/ui/status/bluetooth.js:411 ../js/ui/status/bluetooth.js:444 msgid "Bluetooth Agent" msgstr "" -#: ../js/ui/status/bluetooth.js:371 +#: ../js/ui/status/bluetooth.js:372 #, c-format msgid "Authorization request from %s" msgstr "Richesta autorizzazione da %s" -#: ../js/ui/status/bluetooth.js:377 +#: ../js/ui/status/bluetooth.js:378 #, c-format msgid "Device %s wants access to the service '%s'" msgstr "Il dispositivo %s vuole accedere al servizio «%s»" -#: ../js/ui/status/bluetooth.js:379 +#: ../js/ui/status/bluetooth.js:380 msgid "Always grant access" msgstr "Consenti sempre accesso" -#: ../js/ui/status/bluetooth.js:380 +#: ../js/ui/status/bluetooth.js:381 msgid "Grant this time only" msgstr "Consenti solo stavolta" -#: ../js/ui/status/bluetooth.js:381 +#: ../js/ui/status/bluetooth.js:382 msgid "Reject" msgstr "Rifiuta" -#: ../js/ui/status/bluetooth.js:411 +#: ../js/ui/status/bluetooth.js:412 #, c-format msgid "Pairing confirmation for %s" msgstr "Conferma associazione per %s" -#: ../js/ui/status/bluetooth.js:417 ../js/ui/status/bluetooth.js:451 +#: ../js/ui/status/bluetooth.js:418 ../js/ui/status/bluetooth.js:452 #, c-format msgid "Device %s wants to pair with this computer" msgstr "Il dispositivo %s vuole associarsi con questo computer" -#: ../js/ui/status/bluetooth.js:418 +#: ../js/ui/status/bluetooth.js:419 #, c-format msgid "Please confirm whether the PIN '%s' matches the one on the device." msgstr "Confermare la corrispondenza del PIN «%s» con quello sul dispositivo." -#: ../js/ui/status/bluetooth.js:420 +#: ../js/ui/status/bluetooth.js:421 msgid "Matches" msgstr "Corrisponde" -#: ../js/ui/status/bluetooth.js:421 +#: ../js/ui/status/bluetooth.js:422 msgid "Does not match" msgstr "Non corrisponde" -#: ../js/ui/status/bluetooth.js:444 +#: ../js/ui/status/bluetooth.js:445 #, c-format msgid "Pairing request for %s" msgstr "Richiesta associazione per %s" -#: ../js/ui/status/bluetooth.js:452 +#: ../js/ui/status/bluetooth.js:453 msgid "Please enter the PIN mentioned on the device." msgstr "Inserire il PIN indicato sul dispositivo." -#: ../js/ui/status/bluetooth.js:468 +#: ../js/ui/status/bluetooth.js:469 msgid "OK" msgstr "OK" @@ -867,7 +877,7 @@ msgstr "%s non è disponibile." #: ../js/ui/telepathyClient.js:666 #, no-c-format msgid "Sent at %X on %A" -msgstr "Inviato alle %-H.%M.%S di %A" +msgstr "Inviato alle %-H.%M di %A" # FIXME ma ha senso in inglese??? #: ../js/ui/viewSelector.js:26 @@ -919,32 +929,32 @@ msgstr[1] "%u ingressi" msgid "System Sounds" msgstr "Audio di sistema" -#: ../src/shell-global.c:1233 +#: ../src/shell-global.c:1366 msgid "Less than a minute ago" msgstr "Meno di un minuto fa" -#: ../src/shell-global.c:1237 +#: ../src/shell-global.c:1370 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minuto fa" msgstr[1] "%d minuti fa" -#: ../src/shell-global.c:1242 +#: ../src/shell-global.c:1375 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d ora fa" msgstr[1] "%d ore fa" -#: ../src/shell-global.c:1247 +#: ../src/shell-global.c:1380 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d giorno fa" msgstr[1] "%d giorni fa" -#: ../src/shell-global.c:1252 +#: ../src/shell-global.c:1385 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" diff --git a/src/Makefile-st.am b/src/Makefile-st.am index 225ff5bc2..cb54ef4ba 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -159,11 +159,17 @@ st_source_c = \ st/st-widget.c \ $(NULL) +st_non_gir_sources = \ + st/st-scroll-view-fade.c \ + st/st-scroll-view-fade.h \ + $(NULL) + noinst_LTLIBRARIES += libst-1.0.la libst_1_0_la_LIBADD = -lm $(ST_LIBS) libst_1_0_la_SOURCES = \ - $(st_source_c) \ + $(st_source_c) \ + $(st_non_gir_sources) \ $(st_source_private_h) \ $(st_source_private_c) \ $(st_source_h) \ diff --git a/src/Makefile.am b/src/Makefile.am index f252e5bc1..5bfa8c764 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -262,7 +262,7 @@ St-1.0.gir: $(mutter) $(G_IR_SCANNER) libst-1.0.la Makefile --libtool="$(LIBTOOL)" \ --library=libst-1.0.la \ -DST_COMPILATION \ - $(filter-out %-private.h, $(addprefix $(srcdir)/,$(st_source_h))) \ + $(filter-out %-private.h $(st_non_gir_sources), $(addprefix $(srcdir)/,$(st_source_h))) \ $(addprefix $(srcdir)/,$(st_source_c)) \ $(srcdir)/st-enum-types.h \ $(st_cflags) \ diff --git a/src/gdmuser/gdm-user-manager.c b/src/gdmuser/gdm-user-manager.c index a39bea1ca..eba9da11e 100644 --- a/src/gdmuser/gdm-user-manager.c +++ b/src/gdmuser/gdm-user-manager.c @@ -1020,13 +1020,10 @@ on_get_unix_user_finished (DBusGProxy *proxy, DBusGProxyCall *call, GdmUserManagerNewSession *new_session) { - GdmUserManager *manager; GError *error; guint uid; gboolean res; - manager = new_session->manager; - g_assert (new_session->get_unix_user_call == call); error = NULL; @@ -1550,11 +1547,9 @@ static void get_accounts_proxy (GdmUserManager *manager) { DBusGProxy *proxy; - GError *error; g_assert (manager->priv->accounts_proxy == NULL); - error = NULL; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, ACCOUNTS_NAME, ACCOUNTS_PATH, @@ -2554,8 +2549,6 @@ reload_shells (GdmUserManager *manager) static void load_users_manually (GdmUserManager *manager) { - gboolean res; - manager->priv->shells = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, @@ -2564,7 +2557,7 @@ load_users_manually (GdmUserManager *manager) load_sessions (manager); - res = load_ck_history (manager); + load_ck_history (manager); schedule_reload_passwd (manager); } @@ -2605,10 +2598,8 @@ load_seat_incrementally (GdmUserManager *manager) } if (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_LOADED) { - gboolean res; - load_sessions (manager); - res = load_ck_history (manager); + load_ck_history (manager); } maybe_set_is_loaded (manager); diff --git a/src/gdmuser/gdm-user.c b/src/gdmuser/gdm-user.c index 710510a44..735c2cb74 100644 --- a/src/gdmuser/gdm-user.c +++ b/src/gdmuser/gdm-user.c @@ -151,10 +151,6 @@ gdm_user_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GdmUser *user; - - user = GDM_USER (object); - switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); diff --git a/src/gvc/gvc-mixer-event-role.c b/src/gvc/gvc-mixer-event-role.c index 7eb3d00d7..071722e48 100644 --- a/src/gvc/gvc-mixer-event-role.c +++ b/src/gvc/gvc-mixer-event-role.c @@ -59,13 +59,10 @@ update_settings (GvcMixerEventRole *role, gpointer *op) { pa_operation *o; - guint index; const GvcChannelMap *map; pa_context *context; pa_ext_stream_restore_info info; - index = gvc_mixer_stream_get_index (GVC_MIXER_STREAM (role)); - map = gvc_mixer_stream_get_channel_map (GVC_MIXER_STREAM(role)); info.volume = *gvc_channel_map_get_cvolume(map); @@ -165,12 +162,9 @@ gvc_mixer_event_role_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerEventRole *self; object = G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_EVENT_ROLE (object); - return object; } diff --git a/src/gvc/gvc-mixer-sink-input.c b/src/gvc/gvc-mixer-sink-input.c index 9429eca0a..5cd665c9c 100644 --- a/src/gvc/gvc-mixer-sink-input.c +++ b/src/gvc/gvc-mixer-sink-input.c @@ -55,12 +55,10 @@ gvc_mixer_sink_input_push_volume (GvcMixerStream *stream, gpointer *op) const GvcChannelMap *map; pa_context *context; const pa_cvolume *cv; - guint num_channels; index = gvc_mixer_stream_get_index (stream); map = gvc_mixer_stream_get_channel_map (stream); - num_channels = gvc_channel_map_get_num_channels (map); cv = gvc_channel_map_get_cvolume(map); @@ -115,12 +113,9 @@ gvc_mixer_sink_input_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSinkInput *self; object = G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SINK_INPUT (object); - return object; } @@ -149,13 +144,9 @@ gvc_mixer_sink_input_init (GvcMixerSinkInput *sink_input) static void gvc_mixer_sink_input_dispose (GObject *object) { - GvcMixerSinkInput *mixer_sink_input; - g_return_if_fail (object != NULL); g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); - mixer_sink_input = GVC_MIXER_SINK_INPUT (object); - G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->dispose (object); } diff --git a/src/gvc/gvc-mixer-sink.c b/src/gvc/gvc-mixer-sink.c index 30fceac4d..5b74a5ec6 100644 --- a/src/gvc/gvc-mixer-sink.c +++ b/src/gvc/gvc-mixer-sink.c @@ -145,12 +145,9 @@ gvc_mixer_sink_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSink *self; object = G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SINK (object); - return object; } @@ -180,13 +177,9 @@ gvc_mixer_sink_init (GvcMixerSink *sink) static void gvc_mixer_sink_dispose (GObject *object) { - GvcMixerSink *mixer_sink; - g_return_if_fail (object != NULL); g_return_if_fail (GVC_IS_MIXER_SINK (object)); - mixer_sink = GVC_MIXER_SINK (object); - G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->dispose (object); } diff --git a/src/gvc/gvc-mixer-source-output.c b/src/gvc/gvc-mixer-source-output.c index 636fc2ea7..8d76763bb 100644 --- a/src/gvc/gvc-mixer-source-output.c +++ b/src/gvc/gvc-mixer-source-output.c @@ -66,12 +66,9 @@ gvc_mixer_source_output_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSourceOutput *self; object = G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SOURCE_OUTPUT (object); - return object; } diff --git a/src/gvc/gvc-mixer-source.c b/src/gvc/gvc-mixer-source.c index 46d640380..6fed25e7b 100644 --- a/src/gvc/gvc-mixer-source.c +++ b/src/gvc/gvc-mixer-source.c @@ -145,12 +145,9 @@ gvc_mixer_source_constructor (GType type, GObjectConstructParam *construct_params) { GObject *object; - GvcMixerSource *self; object = G_OBJECT_CLASS (gvc_mixer_source_parent_class)->constructor (type, n_construct_properties, construct_params); - self = GVC_MIXER_SOURCE (object); - return object; } @@ -180,13 +177,9 @@ gvc_mixer_source_init (GvcMixerSource *source) static void gvc_mixer_source_dispose (GObject *object) { - GvcMixerSource *mixer_source; - g_return_if_fail (object != NULL); g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); - mixer_source = GVC_MIXER_SOURCE (object); - G_OBJECT_CLASS (gvc_mixer_source_parent_class)->dispose (object); } diff --git a/src/shell-app-system.c b/src/shell-app-system.c index daf0f14cc..0cf0bbb8b 100644 --- a/src/shell-app-system.c +++ b/src/shell-app-system.c @@ -1317,7 +1317,6 @@ shell_app_info_launch_full (ShellAppInfo *info, gboolean ret; ShellGlobal *global; MetaScreen *screen; - MetaDisplay *display; if (startup_id) *startup_id = NULL; @@ -1353,7 +1352,6 @@ shell_app_info_launch_full (ShellAppInfo *info, global = shell_global_get (); screen = shell_global_get_screen (global); - display = meta_screen_get_display (screen); if (timestamp == 0) timestamp = clutter_get_current_event_time (); diff --git a/src/shell-app-usage.c b/src/shell-app-usage.c index 6126dc096..e8fe3ae8a 100644 --- a/src/shell-app-usage.c +++ b/src/shell-app-usage.c @@ -324,7 +324,8 @@ on_app_state_changed (ShellWindowTracker *tracker, running = shell_app_get_state (app) == SHELL_APP_STATE_RUNNING; - usage->last_seen = get_time (); + if (running) + usage->last_seen = get_time (); } static void diff --git a/src/shell-doc-system.c b/src/shell-doc-system.c index dab64e9a0..3ee51e6ba 100644 --- a/src/shell-doc-system.c +++ b/src/shell-doc-system.c @@ -285,7 +285,8 @@ shell_doc_system_open (ShellDocSystem *system, app_exec_quoted = g_regex_replace (regex, app_exec, -1, 0, "%%", 0, NULL); g_regex_unref (regex); - app_info = g_app_info_create_from_commandline (app_exec, NULL, 0, NULL); + app_info = g_app_info_create_from_commandline (app_exec_quoted, NULL, 0, NULL); + g_free (app_exec_quoted); /* The point of passing an app launch context to launch() is mostly to get startup notification and diff --git a/src/shell-global.c b/src/shell-global.c index 8e0463ddd..68ee6190c 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -885,7 +885,6 @@ shell_global_add_extension_importer (ShellGlobal *global, GError **error) { jsval target_object; - JSObject *importer; JSContext *context = gjs_context_get_native_context (global->js_context); char *search_path[2] = { 0, 0 }; @@ -920,7 +919,7 @@ shell_global_add_extension_importer (ShellGlobal *global, } search_path[0] = (char*)directory; - importer = gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE); + gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE); JS_EndRequest (context); return TRUE; out_error: diff --git a/src/shell-recorder.c b/src/shell-recorder.c index a129f7d7b..a1c626b41 100644 --- a/src/shell-recorder.c +++ b/src/shell-recorder.c @@ -83,6 +83,7 @@ struct _ShellRecorder { guint redraw_idle; guint update_memory_used_timeout; guint update_pointer_timeout; + guint repaint_hook_id; }; struct _RecorderPipeline @@ -146,11 +147,11 @@ G_DEFINE_TYPE(ShellRecorder, shell_recorder, G_TYPE_OBJECT); * (Theora does have some support for frames at non-uniform times, but * things seem to break down if there are large gaps.) */ -#define DEFAULT_PIPELINE "videorate ! theoraenc ! oggmux" +#define DEFAULT_PIPELINE "videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux" -/* The default filename pattern. Example shell-20090311b-2.ogg +/* The default filename pattern. Example shell-20090311b-2.webm */ -#define DEFAULT_FILENAME "shell-%d%u-%c.ogg" +#define DEFAULT_FILENAME "shell-%d%u-%c.webm" /* If we can find the amount of memory on the machine, we use half * of that for memory_target, otherwise, we use this value, in kB. @@ -239,6 +240,22 @@ get_memory_target (void) return DEFAULT_MEMORY_TARGET; } +/* + * Used to force full stage redraws during recording to avoid artifacts + * + * Note: That this will cause the stage to be repainted on + * every animation frame even if the frame wouldn't normally cause any new + * drawing + */ +static gboolean +recorder_repaint_hook (gpointer data) +{ + ClutterActor *stage = data; + clutter_actor_queue_redraw (stage); + + return TRUE; +} + static void shell_recorder_init (ShellRecorder *recorder) { @@ -1476,11 +1493,47 @@ recorder_pipeline_closed (RecorderPipeline *pipeline) recorder_pipeline_free (pipeline); } +/* + * Replaces '%T' in the passed pipeline with the thread count, + * the maximum possible value is 64 (limit of what vp8enc supports) + * + * It is assumes that %T occurs only once. + */ +static char* +substitute_thread_count (const char *pipeline) +{ + char *tmp; + int n_threads; + GString *result; + + tmp = strstr (pipeline, "%T"); + + if (!tmp) + return g_strdup (pipeline); + +#ifdef _SC_NPROCESSORS_ONLN + { + int n_processors = sysconf (_SC_NPROCESSORS_ONLN); /* includes hyper-threading */ + n_threads = MIN (MAX (1, n_processors - 1), 64); + } +#else + n_threads = 3; +#endif + + result = g_string_new (NULL); + g_string_append_len (result, pipeline, tmp - pipeline); + g_string_append_printf (result, "%d", n_threads); + g_string_append (result, tmp + 2); + + return g_string_free (result, FALSE);; +} + static gboolean recorder_open_pipeline (ShellRecorder *recorder) { RecorderPipeline *pipeline; const char *pipeline_description; + char *parsed_pipeline; GError *error = NULL; GstBus *bus; @@ -1492,9 +1545,12 @@ recorder_open_pipeline (ShellRecorder *recorder) if (!pipeline_description) pipeline_description = DEFAULT_PIPELINE; - pipeline->pipeline = gst_parse_launch_full (pipeline_description, NULL, + parsed_pipeline = substitute_thread_count (pipeline_description); + + pipeline->pipeline = gst_parse_launch_full (parsed_pipeline, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &error); + g_free (parsed_pipeline); if (pipeline->pipeline == NULL) { @@ -1682,6 +1738,9 @@ shell_recorder_record (ShellRecorder *recorder) recorder->state = RECORDER_STATE_RECORDING; recorder_add_update_pointer_timeout (recorder); + /* Set up repaint hook */ + recorder->repaint_hook_id = clutter_threads_add_repaint_func(recorder_repaint_hook, recorder->stage, NULL); + /* Record an initial frame and also redraw with the indicator */ clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage)); @@ -1723,6 +1782,12 @@ shell_recorder_pause (ShellRecorder *recorder) /* Queue a redraw to remove the recording indicator */ clutter_actor_queue_redraw (CLUTTER_ACTOR (recorder->stage)); + + if (recorder->repaint_hook_id != 0) + { + clutter_threads_remove_repaint_func (recorder->repaint_hook_id); + recorder->repaint_hook_id = 0; + } } /** diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c index 1e61b7c6d..c2ff93281 100644 --- a/src/shell-window-tracker.c +++ b/src/shell-window-tracker.c @@ -825,12 +825,9 @@ on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellWindowTracker *tracker) { - MetaScreen *screen; MetaWindow *new_focus_win; ShellApp *new_focus_app; - screen = shell_global_get_screen (shell_global_get ()); - new_focus_win = meta_display_get_focus_window (display); new_focus_app = new_focus_win ? g_hash_table_lookup (tracker->window_to_app, new_focus_win) : NULL; diff --git a/src/st/st-icon.c b/src/st/st-icon.c index 78519c483..d1210f156 100644 --- a/src/st/st-icon.c +++ b/src/st/st-icon.c @@ -62,6 +62,7 @@ struct _StIconPrivate CoglHandle shadow_material; float shadow_width; float shadow_height; + StShadow *shadow_spec; }; static void st_icon_update (StIcon *icon); @@ -164,6 +165,12 @@ st_icon_dispose (GObject *gobject) priv->shadow_material = COGL_INVALID_HANDLE; } + if (priv->shadow_spec) + { + st_shadow_unref (priv->shadow_spec); + priv->shadow_spec = NULL; + } + G_OBJECT_CLASS (st_icon_parent_class)->dispose (gobject); } @@ -249,8 +256,6 @@ st_icon_paint (ClutterActor *actor) { if (priv->shadow_material) { - StThemeNode *node = st_widget_get_theme_node (ST_WIDGET (actor)); - StShadow *shadow_spec = st_theme_node_get_shadow (node); ClutterActorBox allocation; float width, height; @@ -262,7 +267,7 @@ st_icon_paint (ClutterActor *actor) allocation.x2 = allocation.x1 + priv->shadow_width; allocation.y2 = allocation.y1 + priv->shadow_height; - _st_paint_shadow_with_opacity (shadow_spec, + _st_paint_shadow_with_opacity (priv->shadow_spec, priv->shadow_material, &allocation, clutter_actor_get_paint_opacity (priv->icon_texture)); @@ -279,6 +284,13 @@ st_icon_style_changed (StWidget *widget) StThemeNode *theme_node = st_widget_get_theme_node (widget); StIconPrivate *priv = self->priv; + if (priv->shadow_spec) + { + st_shadow_unref (priv->shadow_spec); + priv->shadow_spec = NULL; + } + priv->shadow_spec = st_theme_node_get_shadow (theme_node, "icon-shadow"); + priv->theme_icon_size = (int)(0.5 + st_theme_node_get_length (theme_node, "icon-size")); st_icon_update_icon_size (self); st_icon_update (self); @@ -353,8 +365,6 @@ static void st_icon_update_shadow_material (StIcon *icon) { StIconPrivate *priv = icon->priv; - StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (icon)); - StShadow *shadow_spec = st_theme_node_get_shadow (theme_node); if (priv->shadow_material) { @@ -362,14 +372,15 @@ st_icon_update_shadow_material (StIcon *icon) priv->shadow_material = COGL_INVALID_HANDLE; } - if (shadow_spec) + if (priv->shadow_spec) { CoglHandle material; gint width, height; clutter_texture_get_base_size (CLUTTER_TEXTURE (priv->icon_texture), &width, &height); - material = _st_create_shadow_material_from_actor (shadow_spec, + + material = _st_create_shadow_material_from_actor (priv->shadow_spec, priv->icon_texture); priv->shadow_material = material; priv->shadow_width = width; diff --git a/src/st/st-overflow-box.c b/src/st/st-overflow-box.c index 9996b2a18..643a0e968 100644 --- a/src/st/st-overflow-box.c +++ b/src/st/st-overflow-box.c @@ -250,7 +250,7 @@ st_overflow_box_allocate (ClutterActor *actor, StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); ClutterActorBox content_box; gfloat position; - float avail_width, avail_height; + float avail_width; GList *l, *children; int i; gboolean done_non_fixed; @@ -265,7 +265,6 @@ st_overflow_box_allocate (ClutterActor *actor, st_theme_node_get_content_box (theme_node, box, &content_box); avail_width = content_box.x2 - content_box.x1; - avail_height = content_box.y2 - content_box.y1; position = content_box.y1; priv->n_visible = 0; diff --git a/src/st/st-private.c b/src/st/st-private.c index f4707691e..54d9a4b5f 100644 --- a/src/st/st-private.c +++ b/src/st/st-private.c @@ -49,7 +49,7 @@ _st_actor_get_preferred_width (ClutterActor *actor, ClutterRequestMode mode; gfloat natural_height; - g_object_get (G_OBJECT (actor), "request-mode", &mode, NULL); + mode = clutter_actor_get_request_mode (actor); if (mode == CLUTTER_REQUEST_WIDTH_FOR_HEIGHT) { clutter_actor_get_preferred_height (actor, -1, NULL, &natural_height); @@ -86,7 +86,7 @@ _st_actor_get_preferred_height (ClutterActor *actor, ClutterRequestMode mode; gfloat natural_width; - g_object_get (G_OBJECT (actor), "request-mode", &mode, NULL); + mode = clutter_actor_get_request_mode (actor); if (mode == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) { clutter_actor_get_preferred_width (actor, -1, NULL, &natural_width); @@ -173,8 +173,7 @@ _st_allocate_fill (StWidget *parent, * modified to cope with the fact that the available size may be * less than the preferred size. */ - request = CLUTTER_REQUEST_HEIGHT_FOR_WIDTH; - g_object_get (G_OBJECT (child), "request-mode", &request, NULL); + request = clutter_actor_get_request_mode (child); if (request == CLUTTER_REQUEST_HEIGHT_FOR_WIDTH) { @@ -423,44 +422,31 @@ calculate_gaussian_kernel (gdouble sigma, return ret; } -CoglHandle -_st_create_shadow_material (StShadow *shadow_spec, - CoglHandle src_texture) +static guchar * +blur_pixels (guchar *pixels_in, + gint width_in, + gint height_in, + gint rowstride_in, + gdouble blur, + gint *width_out, + gint *height_out, + gint *rowstride_out) { - static CoglHandle shadow_material_template = COGL_INVALID_HANDLE; - - CoglHandle material; - CoglHandle texture; - guchar *pixels_in, *pixels_out; - gint width_in, height_in, rowstride_in; - gint width_out, height_out, rowstride_out; - float sigma; - - g_return_val_if_fail (shadow_spec != NULL, COGL_INVALID_HANDLE); - g_return_val_if_fail (src_texture != COGL_INVALID_HANDLE, - COGL_INVALID_HANDLE); + guchar *pixels_out; + float sigma; /* we use an approximation of the sigma - blur radius relationship used in Firefox for doing SVG blurs; see http://mxr.mozilla.org/mozilla-central/source/gfx/thebes/src/gfxBlur.cpp#280 */ - sigma = shadow_spec->blur / 1.9; + sigma = blur / 1.9; - width_in = cogl_texture_get_width (src_texture); - height_in = cogl_texture_get_height (src_texture); - rowstride_in = (width_in + 3) & ~3; - - pixels_in = g_malloc0 (rowstride_in * height_in); - - cogl_texture_get_data (src_texture, COGL_PIXEL_FORMAT_A_8, - rowstride_in, pixels_in); - - if ((guint) shadow_spec->blur == 0) + if ((guint) blur == 0) { - width_out = width_in; - height_out = height_in; - rowstride_out = rowstride_in; - pixels_out = g_memdup (pixels_in, rowstride_out * height_out); + *width_out = width_in; + *height_out = height_in; + *rowstride_out = rowstride_in; + pixels_out = g_memdup (pixels_in, *rowstride_out * *height_out); } else { @@ -472,18 +458,18 @@ _st_create_shadow_material (StShadow *shadow_spec, n_values = (gint) 5 * sigma; half = n_values / 2; - width_out = width_in + 2 * half; - height_out = height_in + 2 * half; - rowstride_out = (width_out + 3) & ~3; + *width_out = width_in + 2 * half; + *height_out = height_in + 2 * half; + *rowstride_out = (*width_out + 3) & ~3; - pixels_out = g_malloc0 (rowstride_out * height_out); - line = g_malloc0 (rowstride_out); + pixels_out = g_malloc0 (*rowstride_out * *height_out); + line = g_malloc0 (*rowstride_out); kernel = calculate_gaussian_kernel (sigma, n_values); /* vertical blur */ for (x_in = 0; x_in < width_in; x_in++) - for (y_out = 0; y_out < height_out; y_out++) + for (y_out = 0; y_out < *height_out; y_out++) { guchar *pixel_in, *pixel_out; gint i0, i1; @@ -497,7 +483,7 @@ _st_create_shadow_material (StShadow *shadow_spec, i1 = MIN (height_in + half - y_in, n_values); pixel_in = pixels_in + (y_in + i0 - half) * rowstride_in + x_in; - pixel_out = pixels_out + y_out * rowstride_out + (x_in + half); + pixel_out = pixels_out + y_out * *rowstride_out + (x_in + half); for (i = i0; i < i1; i++) { @@ -507,11 +493,11 @@ _st_create_shadow_material (StShadow *shadow_spec, } /* horizontal blur */ - for (y_out = 0; y_out < height_out; y_out++) + for (y_out = 0; y_out < *height_out; y_out++) { - memcpy (line, pixels_out + y_out * rowstride_out, rowstride_out); + memcpy (line, pixels_out + y_out * *rowstride_out, *rowstride_out); - for (x_out = 0; x_out < width_out; x_out++) + for (x_out = 0; x_out < *width_out; x_out++) { gint i0, i1; guchar *pixel_out, *pixel_in; @@ -520,10 +506,10 @@ _st_create_shadow_material (StShadow *shadow_spec, * full i range [0, n_values) so that x is in [0, width_out). */ i0 = MAX (half - x_out, 0); - i1 = MIN (width_out + half - x_out, n_values); + i1 = MIN (*width_out + half - x_out, n_values); pixel_in = line + x_out + i0 - half; - pixel_out = pixels_out + rowstride_out * y_out + x_out; + pixel_out = pixels_out + *rowstride_out * y_out + x_out; *pixel_out = 0; for (i = i0; i < i1; i++) @@ -537,6 +523,39 @@ _st_create_shadow_material (StShadow *shadow_spec, g_free (line); } + return pixels_out; +} + +CoglHandle +_st_create_shadow_material (StShadow *shadow_spec, + CoglHandle src_texture) +{ + static CoglHandle shadow_material_template = COGL_INVALID_HANDLE; + + CoglHandle material; + CoglHandle texture; + guchar *pixels_in, *pixels_out; + gint width_in, height_in, rowstride_in; + gint width_out, height_out, rowstride_out; + + g_return_val_if_fail (shadow_spec != NULL, COGL_INVALID_HANDLE); + g_return_val_if_fail (src_texture != COGL_INVALID_HANDLE, + COGL_INVALID_HANDLE); + + width_in = cogl_texture_get_width (src_texture); + height_in = cogl_texture_get_height (src_texture); + rowstride_in = (width_in + 3) & ~3; + + pixels_in = g_malloc0 (rowstride_in * height_in); + + cogl_texture_get_data (src_texture, COGL_PIXEL_FORMAT_A_8, + rowstride_in, pixels_in); + + pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in, + shadow_spec->blur, + &width_out, &height_out, &rowstride_out); + g_free (pixels_in); + texture = cogl_texture_new_from_data (width_out, height_out, COGL_TEXTURE_NONE, @@ -545,7 +564,6 @@ _st_create_shadow_material (StShadow *shadow_spec, rowstride_out, pixels_out); - g_free (pixels_in); g_free (pixels_out); if (G_UNLIKELY (shadow_material_template == COGL_INVALID_HANDLE)) @@ -618,6 +636,103 @@ _st_create_shadow_material_from_actor (StShadow *shadow_spec, return shadow_material; } +cairo_pattern_t * +_st_create_shadow_cairo_pattern (StShadow *shadow_spec, + cairo_pattern_t *src_pattern) +{ + cairo_t *cr; + cairo_surface_t *src_surface; + cairo_surface_t *surface_in; + cairo_surface_t *surface_out; + cairo_pattern_t *dst_pattern; + guchar *pixels_in, *pixels_out; + gint width_in, height_in, rowstride_in; + gint width_out, height_out, rowstride_out; + cairo_matrix_t shadow_matrix; + + g_return_val_if_fail (shadow_spec != NULL, NULL); + g_return_val_if_fail (src_pattern != NULL, NULL); + + cairo_pattern_get_surface (src_pattern, &src_surface); + + width_in = cairo_image_surface_get_width (src_surface); + height_in = cairo_image_surface_get_height (src_surface); + + /* We want the output to be a color agnostic alpha mask, + * so we need to strip the color channels from the input + */ + if (cairo_image_surface_get_format (src_surface) != CAIRO_FORMAT_A8) + { + surface_in = cairo_image_surface_create (CAIRO_FORMAT_A8, + width_in, height_in); + + cr = cairo_create (surface_in); + cairo_set_source_surface (cr, src_surface, 0, 0); + cairo_paint (cr); + cairo_destroy (cr); + } + else + { + surface_in = cairo_surface_reference (src_surface); + } + + pixels_in = cairo_image_surface_get_data (surface_in); + rowstride_in = cairo_image_surface_get_stride (surface_in); + + pixels_out = blur_pixels (pixels_in, width_in, height_in, rowstride_in, + shadow_spec->blur, + &width_out, &height_out, &rowstride_out); + cairo_surface_destroy (surface_in); + + surface_out = cairo_image_surface_create_for_data (pixels_out, + CAIRO_FORMAT_A8, + width_out, + height_out, + rowstride_out); + + dst_pattern = cairo_pattern_create_for_surface (surface_out); + cairo_surface_destroy (surface_out); + + cairo_pattern_get_matrix (src_pattern, &shadow_matrix); + + /* Read all the code from the cairo_pattern_set_matrix call + * at the end of this function to here from bottom to top, + * because each new affine transformation is applied in + * front of all the previous ones */ + + /* 6. Invert the matrix back */ + cairo_matrix_invert (&shadow_matrix); + + /* 5. Adjust based on specified offsets */ + cairo_matrix_translate (&shadow_matrix, + shadow_spec->xoffset, + shadow_spec->yoffset); + + /* 4. Recenter the newly scaled image */ + cairo_matrix_translate (&shadow_matrix, + - shadow_spec->spread, + - shadow_spec->spread); + + /* 3. Scale up the blurred image to fill the spread */ + cairo_matrix_scale (&shadow_matrix, + (width_in + 2.0 * shadow_spec->spread) / width_in, + (height_in + 2.0 * shadow_spec->spread) / height_in); + + /* 2. Shift the blurred image left, so that it aligns centered + * under the unblurred one */ + cairo_matrix_translate (&shadow_matrix, + - (width_out - width_in) / 2.0, + - (height_out - height_in) / 2.0); + + /* 1. Invert the matrix so we can work with it in pattern space + */ + cairo_matrix_invert (&shadow_matrix); + + cairo_pattern_set_matrix (dst_pattern, &shadow_matrix); + + return dst_pattern; +} + void _st_paint_shadow_with_opacity (StShadow *shadow_spec, CoglHandle shadow_material, diff --git a/src/st/st-private.h b/src/st/st-private.h index f958842d3..495475742 100644 --- a/src/st/st-private.h +++ b/src/st/st-private.h @@ -24,6 +24,7 @@ #define __ST_PRIVATE_H__ #include +#include #include "st-widget.h" #include "st-bin.h" #include "st-shadow.h" @@ -79,6 +80,9 @@ CoglHandle _st_create_shadow_material (StShadow *shadow_spec, CoglHandle src_texture); CoglHandle _st_create_shadow_material_from_actor (StShadow *shadow_spec, ClutterActor *actor); +cairo_pattern_t *_st_create_shadow_cairo_pattern (StShadow *shadow_spec, + cairo_pattern_t *src_pattern); + void _st_paint_shadow_with_opacity (StShadow *shadow_spec, CoglHandle shadow_material, ClutterActorBox *box, diff --git a/src/st/st-scroll-bar.c b/src/st/st-scroll-bar.c index 61acdf06e..46c974d9c 100644 --- a/src/st/st-scroll-bar.c +++ b/src/st/st-scroll-bar.c @@ -643,13 +643,11 @@ st_scroll_bar_constructor (GType type, GObjectClass *gobject_class; GObject *obj; StScrollBar *bar; - StScrollBarPrivate *priv; gobject_class = G_OBJECT_CLASS (st_scroll_bar_parent_class); obj = gobject_class->constructor (type, n_properties, properties); bar = ST_SCROLL_BAR (obj); - priv = ST_SCROLL_BAR_GET_PRIVATE (bar); g_signal_connect (bar, "notify::reactive", G_CALLBACK (bar_reactive_notify_cb), NULL); diff --git a/src/st/st-scroll-view-fade.c b/src/st/st-scroll-view-fade.c new file mode 100644 index 000000000..050cea5a7 --- /dev/null +++ b/src/st/st-scroll-view-fade.c @@ -0,0 +1,348 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-scroll-view-fade.h: Edge fade effect for StScrollView + * + * Copyright 2010 Intel Corporation. + * Copyright 2011 Adel Gadllah + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + +#define ST_SCROLL_VIEW_FADE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFadeClass)) +#define ST_IS_SCROLL_VIEW_FADE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_SCROLL_VIEW_FADE)) +#define ST_SCROLL_VIEW_FADE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFadeClass)) + +#include "st-scroll-view-fade.h" +#include "st-scroll-view.h" +#include "st-scroll-bar.h" +#include "st-scrollable.h" + +#include +#include + +typedef struct _StScrollViewFadeClass StScrollViewFadeClass; + +#define FADE_OFFSET 68.0f + +static const gchar *fade_glsl_shader = +"uniform sampler2D tex;\n" +"uniform float height;\n" +"uniform float width;\n" +"uniform float scrollbar_width;\n" +"uniform float offset_bottom;\n" +"uniform float offset_top;\n" +"\n" +"void main ()\n" +"{\n" +" vec4 color = cogl_color_in * texture2D (tex, vec2 (cogl_tex_coord_in[0].xy));\n" +" float y = height * cogl_tex_coord_in[0].y;\n" +" float x = width * cogl_tex_coord_in[0].x;\n" +" float ratio = 0.0;\n" +" float fade_bottom_start = height - offset_bottom;\n" +" \n" +" if (offset_top != 0.0 && y < offset_top && x < (width - scrollbar_width)) {\n" +" ratio = y / offset_top;\n" +" cogl_color_out = color * ratio;\n" +" }\n" +" else if (offset_bottom != 0.0 && y > fade_bottom_start && x < (width - scrollbar_width)) {\n" +" ratio = (height - y)/(height - fade_bottom_start);\n" +" cogl_color_out = color * ratio;\n" +" }\n" +" else { \n" +" cogl_color_out = color;\n" +" }\n" +"}\n"; + +struct _StScrollViewFade +{ + ClutterOffscreenEffect parent_instance; + + /* a back pointer to our actor, so that we can query it */ + ClutterActor *actor; + + CoglHandle shader; + CoglHandle program; + + gint tex_uniform; + gint height_uniform; + gint width_uniform; + gint scrollbar_width_uniform; + gint offset_top_uniform; + gint offset_bottom_uniform; + + StAdjustment *vadjustment; + + guint is_attached : 1; +}; + +struct _StScrollViewFadeClass +{ + ClutterOffscreenEffectClass parent_class; +}; + +G_DEFINE_TYPE (StScrollViewFade, + st_scroll_view_fade, + CLUTTER_TYPE_OFFSCREEN_EFFECT); + +static gboolean +st_scroll_view_fade_pre_paint (ClutterEffect *effect) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect); + ClutterEffectClass *parent_class; + + if (self->shader == COGL_INVALID_HANDLE) + return FALSE; + + if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) + return FALSE; + + if (self->actor == NULL) + return FALSE; + + if (self->program == COGL_INVALID_HANDLE) + self->program = cogl_create_program (); + + if (!self->is_attached) + { + g_assert (self->shader != COGL_INVALID_HANDLE); + g_assert (self->program != COGL_INVALID_HANDLE); + + cogl_program_attach_shader (self->program, self->shader); + cogl_program_link (self->program); + + cogl_handle_unref (self->shader); + + self->is_attached = TRUE; + + self->tex_uniform = + cogl_program_get_uniform_location (self->program, "tex"); + self->height_uniform = + cogl_program_get_uniform_location (self->program, "height"); + self->width_uniform = + cogl_program_get_uniform_location (self->program, "width"); + self->scrollbar_width_uniform = + cogl_program_get_uniform_location (self->program, "scrollbar_width"); + self->offset_top_uniform = + cogl_program_get_uniform_location (self->program, "offset_top"); + self->offset_bottom_uniform = + cogl_program_get_uniform_location (self->program, "offset_bottom"); + } + + parent_class = CLUTTER_EFFECT_CLASS (st_scroll_view_fade_parent_class); + return parent_class->pre_paint (effect); +} + +static CoglHandle +st_scroll_view_fade_create_texture (ClutterOffscreenEffect *effect, + gfloat min_width, + gfloat min_height) +{ + return cogl_texture_new_with_size (min_width, + min_height, + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_RGBA_8888_PRE); +} + +static void +st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect); + ClutterOffscreenEffectClass *parent; + CoglHandle material; + + gdouble value, lower, upper, page_size; + ClutterActor *vscroll = st_scroll_view_get_vscroll_bar (ST_SCROLL_VIEW (self->actor)); + + if (self->program == COGL_INVALID_HANDLE) + goto out; + + st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size); + + if (self->offset_top_uniform > -1) { + if (value > lower + 0.1) + cogl_program_set_uniform_1f (self->program, self->offset_top_uniform, FADE_OFFSET); + else + cogl_program_set_uniform_1f (self->program, self->offset_top_uniform, 0.0f); + } + + if (self->offset_bottom_uniform > -1) { + if (value < upper - page_size - 0.1) + cogl_program_set_uniform_1f (self->program, self->offset_bottom_uniform, FADE_OFFSET); + else + cogl_program_set_uniform_1f (self->program, self->offset_bottom_uniform, 0.0f); + } + + if (self->tex_uniform > -1) + cogl_program_set_uniform_1i (self->program, self->tex_uniform, 0); + if (self->height_uniform > -1) + cogl_program_set_uniform_1f (self->program, self->height_uniform, clutter_actor_get_height (self->actor)); + if (self->width_uniform > -1) + cogl_program_set_uniform_1f (self->program, self->width_uniform, clutter_actor_get_width (self->actor)); + if (self->scrollbar_width_uniform > -1) + cogl_program_set_uniform_1f (self->program, self->scrollbar_width_uniform, clutter_actor_get_width (vscroll)); + + material = clutter_offscreen_effect_get_target (effect); + cogl_material_set_user_program (material, self->program); + +out: + parent = CLUTTER_OFFSCREEN_EFFECT_CLASS (st_scroll_view_fade_parent_class); + parent->paint_target (effect); +} + +static void +on_vadjustment_changed (StAdjustment *adjustment, + ClutterEffect *effect) +{ + gdouble value, lower, upper, page_size; + gboolean needs_fade; + + st_adjustment_get_values (adjustment, &value, &lower, &upper, NULL, NULL, &page_size); + needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1); + + clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (effect), needs_fade); +} + +static void +st_scroll_view_fade_set_actor (ClutterActorMeta *meta, + ClutterActor *actor) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (meta); + ClutterActorMetaClass *parent; + + g_return_if_fail (actor == NULL || ST_IS_SCROLL_VIEW (actor)); + + if (self->shader == COGL_INVALID_HANDLE) + { + clutter_actor_meta_set_enabled (meta, FALSE); + return; + } + + if (self->vadjustment) + { + g_signal_handlers_disconnect_by_func (self->vadjustment, + (gpointer)on_vadjustment_changed, + self); + self->vadjustment = NULL; + } + + if (actor) + { + StScrollView *scroll_view = ST_SCROLL_VIEW (actor); + StScrollBar *vscroll = ST_SCROLL_BAR (st_scroll_view_get_vscroll_bar (scroll_view)); + self->vadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (vscroll)); + + g_signal_connect (self->vadjustment, "changed", + G_CALLBACK (on_vadjustment_changed), + self); + + on_vadjustment_changed (self->vadjustment, CLUTTER_EFFECT (self)); + } + + parent = CLUTTER_ACTOR_META_CLASS (st_scroll_view_fade_parent_class); + parent->set_actor (meta, actor); + + /* we keep a back pointer here, to avoid going through the ActorMeta */ + self->actor = clutter_actor_meta_get_actor (meta); +} + +static void +st_scroll_view_fade_dispose (GObject *gobject) +{ + StScrollViewFade *self = ST_SCROLL_VIEW_FADE (gobject); + + if (self->program != COGL_INVALID_HANDLE) + { + cogl_handle_unref (self->program); + + self->program = COGL_INVALID_HANDLE; + self->shader = COGL_INVALID_HANDLE; + } + + if (self->vadjustment) + { + g_signal_handlers_disconnect_by_func (self->vadjustment, + (gpointer)on_vadjustment_changed, + self); + self->vadjustment = NULL; + } + + self->actor = NULL; + + G_OBJECT_CLASS (st_scroll_view_fade_parent_class)->dispose (gobject); +} + +static void +st_scroll_view_fade_class_init (StScrollViewFadeClass *klass) +{ + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterOffscreenEffectClass *offscreen_class; + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + + gobject_class->dispose = st_scroll_view_fade_dispose; + + meta_class->set_actor = st_scroll_view_fade_set_actor; + + effect_class->pre_paint = st_scroll_view_fade_pre_paint; + + offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass); + offscreen_class->create_texture = st_scroll_view_fade_create_texture; + offscreen_class->paint_target = st_scroll_view_fade_paint_target; +} + + +static void +st_scroll_view_fade_init (StScrollViewFade *self) +{ + static CoglHandle shader = COGL_INVALID_HANDLE; + + if (shader == COGL_INVALID_HANDLE) + { + if (clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL)) + { + shader = cogl_create_shader (COGL_SHADER_TYPE_FRAGMENT); + cogl_shader_source (shader, fade_glsl_shader); + cogl_shader_compile (shader); + if (!cogl_shader_is_compiled (shader)) + { + gchar *log_buf = cogl_shader_get_info_log (shader); + + g_warning (G_STRLOC ": Unable to compile the fade shader: %s", + log_buf); + g_free (log_buf); + + cogl_handle_unref (shader); + shader = COGL_INVALID_HANDLE; + } + } + } + + self->shader = shader; + self->is_attached = FALSE; + self->tex_uniform = -1; + self->height_uniform = -1; + self->width_uniform = -1; + self->scrollbar_width_uniform = -1; + self->offset_top_uniform = -1; + self->offset_bottom_uniform = -1; + + if (shader != COGL_INVALID_HANDLE) + cogl_handle_ref (self->shader); +} + +ClutterEffect * +st_scroll_view_fade_new (void) +{ + return g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL); +} diff --git a/src/st/st-scroll-view-fade.h b/src/st/st-scroll-view-fade.h new file mode 100644 index 000000000..f8ec2d5a2 --- /dev/null +++ b/src/st/st-scroll-view-fade.h @@ -0,0 +1,40 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-scroll-view-fade.h: Edge fade effect for StScrollView + * + * Copyright 2010 Intel Corporation. + * Copyright 2011 Adel Gadllah + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef __ST_SCROLL_VIEW_FADE_H__ +#define __ST_SCROLL_VIEW_FADE_H__ + +#include + +G_BEGIN_DECLS + +#define ST_TYPE_SCROLL_VIEW_FADE (st_scroll_view_fade_get_type ()) +#define ST_SCROLL_VIEW_FADE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFade)) +#define ST_IS_SCROLL_VIEW_FADE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_SCROLL_VIEW_FADE)) + +typedef struct _StScrollViewFade StScrollViewFade; + +GType st_scroll_view_fade_get_type (void) G_GNUC_CONST; + +ClutterEffect *st_scroll_view_fade_new (void); + +G_END_DECLS + +#endif /* __ST_SCROLL_VIEW_FADE_H__ */ diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c index 14df6005b..c56182e11 100644 --- a/src/st/st-scroll-view.c +++ b/src/st/st-scroll-view.c @@ -62,6 +62,7 @@ #include "st-marshal.h" #include "st-scroll-bar.h" #include "st-scrollable.h" +#include "st-scroll-view-fade.h" #include #include @@ -93,20 +94,17 @@ struct _StScrollViewPrivate GtkPolicyType hscrollbar_policy; GtkPolicyType vscrollbar_policy; - ClutterActor *top_shadow; - ClutterActor *bottom_shadow; - gfloat row_size; gfloat column_size; - gboolean vshadows; + gboolean vfade; + StScrollViewFade *vfade_effect; + gboolean row_size_set : 1; gboolean column_size_set : 1; guint mouse_scroll : 1; guint hscrollbar_visible : 1; guint vscrollbar_visible : 1; - guint top_shadow_visible : 1; - guint bottom_shadow_visible : 1; }; enum { @@ -117,7 +115,7 @@ enum { PROP_HSCROLLBAR_POLICY, PROP_VSCROLLBAR_POLICY, PROP_MOUSE_SCROLL, - PROP_VSHADOWS + PROP_VFADE }; static void @@ -145,80 +143,50 @@ st_scroll_view_get_property (GObject *object, case PROP_MOUSE_SCROLL: g_value_set_boolean (value, priv->mouse_scroll); break; - case PROP_VSHADOWS: - g_value_set_boolean (value, priv->vshadows); + case PROP_VFADE: + g_value_set_boolean (value, priv->vfade); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } -static void -update_shadow_visibility (StScrollView *scroll) -{ - StScrollViewPrivate *priv = scroll->priv; - - if (priv->vshadows) - { - gdouble value, lower, upper, page_size; - - st_adjustment_get_values (priv->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size); - - priv->top_shadow_visible = value > lower + 0.1; - priv->bottom_shadow_visible = value < upper - page_size - 0.1; - } - else - { - priv->top_shadow_visible = FALSE; - priv->bottom_shadow_visible = FALSE; - } -} - /** - * st_scroll_view_set_vshadows: + * st_scroll_view_set_vfade: * @self: a #StScrollView - * @vshadows: Whether to enable vertical shadows + * @vfade: Whether to enable the vertical fade effect * - * Sets whether to show shadows at the top and bottom of the area. Shadows - * are omitted when fully scrolled to that edge. + * Sets whether to fade the content at the top and bottom of the area when not + * fully scrolled to that edge. */ void -st_scroll_view_set_vshadows (StScrollView *self, - gboolean vshadows) +st_scroll_view_set_vfade (StScrollView *self, + gboolean vfade) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (self)->priv; - vshadows = vshadows != FALSE; - if (priv->vshadows == vshadows) + vfade = vfade != FALSE; + if (priv->vfade == vfade) return; - priv->vshadows = vshadows; + priv->vfade = vfade; - if (vshadows) + if (vfade) { - if (priv->top_shadow) - { - clutter_actor_show (priv->top_shadow); - clutter_actor_show (priv->bottom_shadow); - } - else - { - priv->top_shadow = g_object_new (ST_TYPE_BIN, "style-class", "top-shadow", NULL); - priv->bottom_shadow = g_object_new (ST_TYPE_BIN, "style-class", "bottom-shadow", NULL); + if (priv->vfade_effect == NULL) + priv->vfade_effect = g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL); - clutter_actor_set_parent (priv->bottom_shadow, CLUTTER_ACTOR (self)); - clutter_actor_set_parent (priv->top_shadow, CLUTTER_ACTOR (self)); - } + clutter_actor_add_effect (CLUTTER_ACTOR (self), CLUTTER_EFFECT (priv->vfade_effect)); } - else + else { - clutter_actor_hide (priv->top_shadow); - clutter_actor_hide (priv->bottom_shadow); + clutter_actor_remove_effect (CLUTTER_ACTOR (self), CLUTTER_EFFECT (priv->vfade_effect)); + priv->vfade_effect = NULL; } - update_shadow_visibility (self); + clutter_actor_queue_redraw (CLUTTER_ACTOR (self)); - g_object_notify (G_OBJECT (self), "vshadows"); + g_object_notify (G_OBJECT (self), "vfade"); } static void @@ -232,8 +200,8 @@ st_scroll_view_set_property (GObject *object, switch (property_id) { - case PROP_VSHADOWS: - st_scroll_view_set_vshadows (self, g_value_get_boolean (value)); + case PROP_VFADE: + st_scroll_view_set_vfade (self, g_value_get_boolean (value)); break; case PROP_MOUSE_SCROLL: st_scroll_view_set_mouse_scrolling (self, @@ -259,6 +227,12 @@ st_scroll_view_dispose (GObject *object) { StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv; + if (priv->vfade_effect) + { + clutter_actor_remove_effect (CLUTTER_ACTOR (object), CLUTTER_EFFECT (priv->vfade_effect)); + priv->vfade_effect = NULL; + } + if (priv->vscroll) clutter_actor_destroy (priv->vscroll); @@ -284,20 +258,6 @@ st_scroll_view_dispose (GObject *object) priv->vadjustment = NULL; } - /* since it's impossible to get a handle to these actors, we can - * just directly unparent them and not go through destroy/remove */ - if (priv->top_shadow) - { - clutter_actor_unparent (priv->top_shadow); - priv->top_shadow = NULL; - } - - if (priv->bottom_shadow) - { - clutter_actor_unparent (priv->bottom_shadow); - priv->bottom_shadow = NULL; - } - G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object); } @@ -314,11 +274,6 @@ st_scroll_view_paint (ClutterActor *actor) clutter_actor_paint (priv->hscroll); if (priv->vscrollbar_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->vscroll)) clutter_actor_paint (priv->vscroll); - - if (priv->top_shadow_visible) - clutter_actor_paint (priv->top_shadow); - if (priv->bottom_shadow_visible) - clutter_actor_paint (priv->bottom_shadow); } static void @@ -532,18 +487,6 @@ st_scroll_view_get_preferred_height (ClutterActor *actor, st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p); } -static gfloat -get_shadow_height (ClutterActor *shadow) -{ - gfloat natural_height; - - /* The shadows are empty StBin and have no height-for-width behavior */ - - clutter_actor_get_preferred_height (shadow, -1, NULL, &natural_height); - - return natural_height; -} - static void st_scroll_view_allocate (ClutterActor *actor, const ClutterActorBox *box, @@ -688,25 +631,6 @@ st_scroll_view_allocate (ClutterActor *actor, if (priv->child) clutter_actor_allocate (priv->child, &child_box, flags); - /* Shadows */ - if (priv->top_shadow && CLUTTER_ACTOR_IS_VISIBLE (priv->top_shadow)) - { - child_box.x1 = content_box.x1; - child_box.y1 = content_box.y1; - child_box.x2 = MAX (child_box.x1, content_box.x2 - sb_width); - child_box.y2 = content_box.y1 + get_shadow_height (priv->top_shadow); - clutter_actor_allocate (priv->top_shadow, &child_box, flags); - } - - if (priv->bottom_shadow && CLUTTER_ACTOR_IS_VISIBLE (priv->bottom_shadow)) - { - child_box.x1 = content_box.x1; - child_box.y1 = content_box.y2 - sb_height - get_shadow_height (priv->bottom_shadow); - child_box.x2 = MAX (content_box.x1, content_box.x2 - sb_width); - child_box.y2 = content_box.y2 - sb_height; - clutter_actor_allocate (priv->bottom_shadow, &child_box, flags); - } - priv->hscrollbar_visible = hscrollbar_visible; priv->vscrollbar_visible = vscrollbar_visible; } @@ -719,12 +643,6 @@ st_scroll_view_style_changed (StWidget *widget) st_widget_style_changed (ST_WIDGET (priv->hscroll)); st_widget_style_changed (ST_WIDGET (priv->vscroll)); - if (priv->top_shadow) - { - st_widget_style_changed (ST_WIDGET (priv->top_shadow)); - st_widget_style_changed (ST_WIDGET (priv->bottom_shadow)); - } - ST_WIDGET_CLASS (st_scroll_view_parent_class)->style_changed (widget); } @@ -857,31 +775,16 @@ st_scroll_view_class_init (StScrollViewClass *klass) PROP_MOUSE_SCROLL, pspec); - pspec = g_param_spec_boolean ("vshadows", + pspec = g_param_spec_boolean ("vfade", "Vertical Shadows", - "Show shadows at the top and and bottom of the area unless fully scrolled to that edge", + "Fade the content at the top and and bottom of the area unless fully scrolled to that edge", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, - PROP_VSHADOWS, + PROP_VFADE, pspec); } -static void -child_adjustment_changed_cb (StAdjustment *adjustment, - StScrollView *scroll) -{ - update_shadow_visibility (scroll); -} - -static void -child_adjustment_notify_value (GObject *gobject, - GParamSpec *pspec, - StScrollView *scroll) -{ - update_shadow_visibility (scroll); -} - static void st_scroll_view_init (StScrollView *self) { @@ -897,10 +800,6 @@ st_scroll_view_init (StScrollView *self) NULL); priv->vadjustment = g_object_new (ST_TYPE_ADJUSTMENT, NULL); - g_signal_connect (priv->vadjustment, "changed", - G_CALLBACK (child_adjustment_changed_cb), self); - g_signal_connect (priv->vadjustment, "notify::value", - G_CALLBACK (child_adjustment_notify_value), self); priv->vscroll = g_object_new (ST_TYPE_SCROLL_BAR, "adjustment", priv->vadjustment, "vertical", TRUE, @@ -988,12 +887,6 @@ st_scroll_view_foreach_with_internals (ClutterContainer *container, if (priv->vscroll != NULL) callback (priv->vscroll, user_data); - - if (priv->top_shadow) - { - callback (priv->top_shadow, user_data); - callback (priv->bottom_shadow, user_data); - } } static void diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h index ddb1ebb7d..f0a03fe9d 100644 --- a/src/st/st-scroll-view.h +++ b/src/st/st-scroll-view.h @@ -84,8 +84,8 @@ void st_scroll_view_set_policy (StScrollView *scroll, GtkPolicyType hscroll, GtkPolicyType vscroll); -void st_scroll_view_set_vshadows (StScrollView *self, - gboolean vshadows); +void st_scroll_view_set_vfade (StScrollView *self, + gboolean vfade); G_END_DECLS diff --git a/src/st/st-table.c b/src/st/st-table.c index 26fb42ed0..b736a5426 100644 --- a/src/st/st-table.c +++ b/src/st/st-table.c @@ -315,12 +315,12 @@ st_table_calculate_col_widths (StTable *table, for (list = children; list; list = list->next) { - gint row, col; + gint col; gfloat w_min, w_pref; gboolean x_expand; StTableChild *meta; ClutterActor *child; - gint col_span, row_span; + gint col_span; child = CLUTTER_ACTOR (list->data); @@ -331,10 +331,8 @@ st_table_calculate_col_widths (StTable *table, /* get child properties */ col = meta->col; - row = meta->row; x_expand = meta->x_expand; col_span = meta->col_span; - row_span = meta->row_span; if (x_expand) is_expand_col[col] = TRUE; @@ -428,7 +426,7 @@ st_table_calculate_row_heights (StTable *table, { gint row, col, cell_width; gfloat h_min, h_pref; - gboolean x_expand, y_expand; + gboolean y_expand; StTableChild *meta; ClutterActor *child; gint col_span, row_span; @@ -443,7 +441,6 @@ st_table_calculate_row_heights (StTable *table, /* get child properties */ col = meta->col; row = meta->row; - x_expand = meta->x_expand; y_expand = meta->y_expand; col_span = meta->col_span; row_span = meta->row_span; diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c index c7906ab9c..52ce02ed0 100644 --- a/src/st/st-texture-cache.c +++ b/src/st/st-texture-cache.c @@ -30,6 +30,7 @@ #define CACHE_PREFIX_GICON "gicon:" #define CACHE_PREFIX_URI "uri:" +#define CACHE_PREFIX_URI_FOR_CAIRO "uri-for-cairo:" #define CACHE_PREFIX_THUMBNAIL_URI "thumbnail-uri:" #define CACHE_PREFIX_RAW_CHECKSUM "raw-checksum:" #define CACHE_PREFIX_COMPRESSED_CHECKSUM "compressed-checksum:" @@ -798,6 +799,27 @@ pixbuf_to_cogl_handle (GdkPixbuf *pixbuf) gdk_pixbuf_get_pixels (pixbuf)); } +static cairo_surface_t * +pixbuf_to_cairo_surface (GdkPixbuf *pixbuf) +{ + cairo_surface_t *dummy_surface; + cairo_pattern_t *pattern; + cairo_surface_t *surface; + cairo_t *cr; + + dummy_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1); + + cr = cairo_create (dummy_surface); + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + pattern = cairo_get_source (cr); + cairo_pattern_get_surface (pattern, &surface); + cairo_surface_reference (surface); + cairo_destroy (cr); + cairo_surface_destroy (dummy_surface); + + return surface; +} + static GdkPixbuf * load_pixbuf_fallback(AsyncTextureLoadData *data) { @@ -1504,6 +1526,45 @@ out: return texdata; } +static cairo_surface_t * +st_texture_cache_load_uri_sync_to_cairo_surface (StTextureCache *cache, + StTextureCachePolicy policy, + const gchar *uri, + int available_width, + int available_height, + GError **error) +{ + cairo_surface_t *surface; + GdkPixbuf *pixbuf; + char *key; + + key = g_strconcat (CACHE_PREFIX_URI_FOR_CAIRO, uri, NULL); + + surface = g_hash_table_lookup (cache->priv->keyed_cache, key); + + if (surface == NULL) + { + pixbuf = impl_load_pixbuf_file (uri, available_width, available_height, error); + if (!pixbuf) + goto out; + + surface = pixbuf_to_cairo_surface (pixbuf); + g_object_unref (pixbuf); + + if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER) + { + cairo_surface_reference (surface); + g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), surface); + } + } + else + cairo_surface_reference (surface); + +out: + g_free (key); + return surface; +} + /** * st_texture_cache_load_uri_sync: * @@ -1582,6 +1643,43 @@ st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache, return texture; } +/** + * st_texture_cache_load_file_to_cairo_surface: + * @cache: A #StTextureCache + * @file_path: Path to a file in supported image format + * + * This function synchronously loads the given file path + * into a cairo surface. On error, a warning is emitted + * and %NULL is returned. + * + * Returns: (transfer full): a new #cairo_surface_t * + */ +cairo_surface_t * +st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache, + const gchar *file_path) +{ + cairo_surface_t *surface; + GFile *file; + char *uri; + GError *error = NULL; + + file = g_file_new_for_path (file_path); + uri = g_file_get_uri (file); + + surface = st_texture_cache_load_uri_sync_to_cairo_surface (cache, ST_TEXTURE_CACHE_POLICY_FOREVER, + uri, -1, -1, &error); + g_object_unref (file); + g_free (uri); + + if (surface == NULL) + { + g_warning ("Failed to load %s: %s", file_path, error->message); + g_clear_error (&error); + return NULL; + } + return surface; +} + /** * st_texture_cache_load_file_simple: * @cache: A #StTextureCache diff --git a/src/st/st-texture-cache.h b/src/st/st-texture-cache.h index 4f1a4820a..5f1e70193 100644 --- a/src/st/st-texture-cache.h +++ b/src/st/st-texture-cache.h @@ -119,6 +119,9 @@ ClutterActor *st_texture_cache_load_uri_sync (StTextureCache *cache, CoglHandle st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache, const gchar *file_path); +cairo_surface_t *st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache, + const gchar *file_path); + ClutterActor *st_texture_cache_load_file_simple (StTextureCache *cache, const gchar *file_path); diff --git a/src/st/st-theme-node-drawing.c b/src/st/st-theme-node-drawing.c index 6e71c9881..52c737ef4 100644 --- a/src/st/st-theme-node-drawing.c +++ b/src/st/st-theme-node-drawing.c @@ -427,37 +427,37 @@ get_arbitrary_border_color (StThemeNode *node, st_theme_node_get_border_color (node, ST_SIDE_TOP, color); } -static CoglHandle -st_theme_node_render_gradient (StThemeNode *node) +static gboolean +st_theme_node_has_visible_outline (StThemeNode *node) +{ + if (node->background_color.alpha > 0) + return TRUE; + + if (node->background_gradient_end.alpha > 0) + return TRUE; + + if (node->border_radius[ST_CORNER_TOPLEFT] > 0 || + node->border_radius[ST_CORNER_TOPRIGHT] > 0 || + node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 || + node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0) + return TRUE; + + if (node->border_width[ST_SIDE_TOP] > 0 || + node->border_width[ST_SIDE_LEFT] > 0 || + node->border_width[ST_SIDE_RIGHT] > 0 || + node->border_width[ST_SIDE_BOTTOM] > 0) + return TRUE; + + return FALSE; +} + +static cairo_pattern_t * +create_cairo_pattern_of_background_gradient (StThemeNode *node) { - CoglHandle texture; - int radius[4], i; - cairo_t *cr; - cairo_surface_t *surface; cairo_pattern_t *pattern; - ClutterColor border_color; - int border_width[4]; - guint rowstride; - guchar *data; - rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, node->alloc_width); - data = g_new0 (guchar, node->alloc_height * rowstride); - surface = cairo_image_surface_create_for_data (data, - CAIRO_FORMAT_ARGB32, - node->alloc_width, - node->alloc_height, - rowstride); - cr = cairo_create (surface); - - /* TODO - support non-uniform border colors */ - get_arbitrary_border_color (node, &border_color); - - for (i = 0; i < 4; i++) - { - border_width[i] = st_theme_node_get_border_width (node, i); - - radius[i] = st_theme_node_get_border_radius (node, i); - } + g_return_val_if_fail (node->background_gradient_type != ST_GRADIENT_NONE, + NULL); if (node->background_gradient_type == ST_GRADIENT_VERTICAL) pattern = cairo_pattern_create_linear (0, 0, 0, node->alloc_height); @@ -482,45 +482,374 @@ st_theme_node_render_gradient (StThemeNode *node) node->background_gradient_end.green / 255., node->background_gradient_end.blue / 255., node->background_gradient_end.alpha / 255.); + return pattern; +} + +static cairo_pattern_t * +create_cairo_pattern_of_background_image (StThemeNode *node, + gboolean *needs_background_fill) +{ + cairo_surface_t *surface; + cairo_pattern_t *pattern; + cairo_content_t content; + cairo_matrix_t matrix; + const char *file; + double height_ratio, width_ratio; + int file_width; + int file_height; + + StTextureCache *texture_cache; + + file = st_theme_node_get_background_image (node); + + texture_cache = st_texture_cache_get_default (); + + surface = st_texture_cache_load_file_to_cairo_surface (texture_cache, file); + + if (surface == NULL) + return NULL; + + g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE); + + content = cairo_surface_get_content (surface); + pattern = cairo_pattern_create_for_surface (surface); + + file_width = cairo_image_surface_get_width (surface); + file_height = cairo_image_surface_get_height (surface); + + height_ratio = file_height / node->alloc_height; + width_ratio = file_width / node->alloc_width; + + *needs_background_fill = TRUE; + if ((file_width > node->alloc_width || file_height > node->alloc_height) + && !node->background_position_set) + { + double scale_factor; + double x_offset, y_offset; + + if (width_ratio > height_ratio) + { + double scaled_height; + + /* center vertically */ + + scale_factor = width_ratio; + scaled_height = file_height / scale_factor; + + x_offset = 0.; + y_offset = - (node->alloc_height / 2. - scaled_height / 2.); + } + else + { + double scaled_width; + + /* center horizontally */ + + scale_factor = height_ratio; + scaled_width = file_width / scale_factor; + + y_offset = 0.; + x_offset = - (node->alloc_width / 2. - scaled_width / 2.); + } + + cairo_matrix_init_translate (&matrix, x_offset, y_offset); + cairo_matrix_scale (&matrix, scale_factor, scale_factor); + + cairo_pattern_set_matrix (pattern, &matrix); + + /* If it's opaque, and when scaled, fills up the entire allocated + * area, then don't bother doing a background fill first + */ + if (content != CAIRO_CONTENT_COLOR_ALPHA && width_ratio == height_ratio) + *needs_background_fill = FALSE; + } + else + { + double x_offset, y_offset; + + if (node->background_position_set) + { + x_offset = -node->background_position_x; + y_offset = -node->background_position_y; + } + else + { + if (node->alloc_width > file_width) + x_offset = - (node->alloc_width / 2.0 - file_width / 2.0); + else + x_offset = - (file_width / 2.0 - node->alloc_width / 2.0); + + if (node->alloc_height > file_height) + y_offset = - (node->alloc_height / 2.0 - file_height / 2.0); + else + y_offset = - (file_height / 2.0 - node->alloc_height / 2.0); + } + + /* If it's opaque, and when translated, fills up the entire allocated + * area, then don't bother doing a background fill first + */ + if (content != CAIRO_CONTENT_COLOR_ALPHA + && -x_offset <= 0 + && -x_offset + file_width >= node->alloc_width + && -y_offset <= 0 + && -y_offset + file_height >= node->alloc_height) + *needs_background_fill = FALSE; + + cairo_matrix_init_translate (&matrix, x_offset, y_offset); + cairo_pattern_set_matrix (pattern, &matrix); + } + + return pattern; +} + +static void +paint_background_image_shadow_to_cairo_context (StThemeNode *node, + StShadow *shadow_spec, + cairo_pattern_t *pattern, + cairo_t *cr, + cairo_path_t *interior_path, + cairo_path_t *outline_path, + int x, + int y, + int width, + int height) +{ + cairo_pattern_t *shadow_pattern; + + g_assert (shadow_spec != NULL); + g_assert (pattern != NULL); + + if (outline_path != NULL) + { + cairo_surface_t *clipped_surface; + cairo_pattern_t *clipped_pattern; + cairo_t *temp_cr; + + /* Prerender the pattern to a temporary surface, + * so it's properly clipped before we create a shadow from it + */ + clipped_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + temp_cr = cairo_create (clipped_surface); + + cairo_set_operator (temp_cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (temp_cr); + cairo_set_operator (temp_cr, CAIRO_OPERATOR_SOURCE); + + if (interior_path != NULL) + { + cairo_append_path (temp_cr, interior_path); + cairo_clip (temp_cr); + } + + cairo_append_path (temp_cr, outline_path); + cairo_translate (temp_cr, x, y); + cairo_set_source (temp_cr, pattern); + cairo_clip (temp_cr); + cairo_paint (temp_cr); + cairo_destroy (temp_cr); + + clipped_pattern = cairo_pattern_create_for_surface (clipped_surface); + cairo_surface_destroy (clipped_surface); + + shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, + clipped_pattern); + cairo_pattern_destroy (clipped_pattern); + } + else + { + shadow_pattern = _st_create_shadow_cairo_pattern (shadow_spec, + pattern); + } + + /* Stamp the shadow pattern out in the appropriate color + * in a new layer + */ + cairo_push_group (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba (cr, + shadow_spec->color.red / 255.0, + shadow_spec->color.green / 255.0, + shadow_spec->color.blue / 255.0, + shadow_spec->color.alpha / 255.0); + cairo_paint (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN); + + cairo_set_source (cr, shadow_pattern); + cairo_paint (cr); + cairo_pattern_destroy (shadow_pattern); + + cairo_pop_group_to_source (cr); + + /* mask and merge the shadow + */ + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_save (cr); + if (interior_path != NULL) + { + /* If there are borders, clip the shadow to the interior + * of the borders + */ + cairo_append_path (cr, interior_path); + cairo_clip (cr); + } + else if (outline_path != NULL) + { + /* If there is a visible outline, clip the shadow to + * that outline + */ + cairo_append_path (cr, outline_path); + cairo_clip (cr); + } + + cairo_paint (cr); + cairo_restore (cr); +} + +/* In order for borders to be smoothly blended with non-solid backgrounds, + * we need to use cairo. This function is a slow fallback path for those + * cases (gradients, background images, etc). + */ +static CoglHandle +st_theme_node_render_background_with_border (StThemeNode *node) +{ + StBorderImage *border_image; + CoglHandle texture; + int radius[4], i; + cairo_t *cr; + cairo_surface_t *surface; + StShadow *shadow_spec; + cairo_pattern_t *pattern = NULL; + cairo_path_t *outline_path = NULL; + gboolean draw_solid_background = TRUE; + gboolean draw_background_image_shadow = FALSE; + gboolean has_visible_outline; + ClutterColor border_color; + int border_width[4]; + guint rowstride; + guchar *data; + ClutterActorBox actor_box; + ClutterActorBox paint_box; + cairo_path_t *interior_path = NULL; + + border_image = st_theme_node_get_border_image (node); + + shadow_spec = st_theme_node_get_background_image_shadow (node); + + actor_box.x1 = 0; + actor_box.x2 = node->alloc_width; + actor_box.y1 = 0; + actor_box.y2 = node->alloc_height; + + /* If there's a background image shadow, we + * may need to create an image bigger than the nodes + * allocation + */ + st_theme_node_get_paint_box (node, &actor_box, &paint_box); + + /* translate the boxes so the paint box is at 0,0 + */ + actor_box.x1 += - paint_box.x1; + actor_box.x2 += - paint_box.x1; + actor_box.y1 += - paint_box.y1; + actor_box.y2 += - paint_box.y1; + + paint_box.x2 += - paint_box.x1; + paint_box.x1 += - paint_box.x1; + paint_box.y2 += - paint_box.y1; + paint_box.y1 += - paint_box.y1; + + rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, + paint_box.x2 - paint_box.x1); + data = g_new0 (guchar, (paint_box.y2 - paint_box.y1) * rowstride); + surface = cairo_image_surface_create_for_data (data, + CAIRO_FORMAT_ARGB32, + paint_box.x2 - paint_box.x1, + paint_box.y2 - paint_box.y1, + rowstride); + cr = cairo_create (surface); + + /* TODO - support non-uniform border colors */ + get_arbitrary_border_color (node, &border_color); + + for (i = 0; i < 4; i++) + { + border_width[i] = st_theme_node_get_border_width (node, i); + + radius[i] = st_theme_node_get_border_radius (node, i); + } + + /* Note we don't support translucent background images on top + * of gradients. It's strictly either/or. + */ + if (node->background_gradient_type != ST_GRADIENT_NONE) + { + pattern = create_cairo_pattern_of_background_gradient (node); + draw_solid_background = FALSE; + } + else + { + const char *background_image; + + background_image = st_theme_node_get_background_image (node); + + if (background_image != NULL) + { + pattern = create_cairo_pattern_of_background_image (node, + &draw_solid_background); + if (shadow_spec && pattern != NULL) + draw_background_image_shadow = TRUE; + } + } + + if (pattern == NULL) + draw_solid_background = TRUE; + + has_visible_outline = st_theme_node_has_visible_outline (node); /* Create a path for the background's outline first */ if (radius[ST_CORNER_TOPLEFT] > 0) cairo_arc (cr, - radius[ST_CORNER_TOPLEFT], - radius[ST_CORNER_TOPLEFT], + actor_box.x1 + radius[ST_CORNER_TOPLEFT], + actor_box.y1 + radius[ST_CORNER_TOPLEFT], radius[ST_CORNER_TOPLEFT], M_PI, 3 * M_PI / 2); else - cairo_move_to (cr, 0, 0); - cairo_line_to (cr, node->alloc_width - radius[ST_CORNER_TOPRIGHT], 0); + cairo_move_to (cr, actor_box.x1, actor_box.y1); + cairo_line_to (cr, actor_box.x2 - radius[ST_CORNER_TOPRIGHT], actor_box.x1); if (radius[ST_CORNER_TOPRIGHT] > 0) cairo_arc (cr, - node->alloc_width - radius[ST_CORNER_TOPRIGHT], - radius[ST_CORNER_TOPRIGHT], + actor_box.x2 - radius[ST_CORNER_TOPRIGHT], + actor_box.x1 + radius[ST_CORNER_TOPRIGHT], radius[ST_CORNER_TOPRIGHT], 3 * M_PI / 2, 2 * M_PI); - cairo_line_to (cr, node->alloc_width, node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT]); + cairo_line_to (cr, actor_box.x2, actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT]); if (radius[ST_CORNER_BOTTOMRIGHT] > 0) cairo_arc (cr, - node->alloc_width - radius[ST_CORNER_BOTTOMRIGHT], - node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT], radius[ST_CORNER_BOTTOMRIGHT], 0, M_PI / 2); - cairo_line_to (cr, radius[ST_CORNER_BOTTOMLEFT], node->alloc_height); + cairo_line_to (cr, actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], actor_box.y2); if (radius[ST_CORNER_BOTTOMLEFT] > 0) cairo_arc (cr, - radius[ST_CORNER_BOTTOMLEFT], - node->alloc_height - radius[ST_CORNER_BOTTOMLEFT], + actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], + actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT], radius[ST_CORNER_BOTTOMLEFT], M_PI / 2, M_PI); cairo_close_path (cr); + outline_path = cairo_copy_path (cr); - /* If we have a border, we fill the outline with the border - * color and create the inline shape for the background gradient; + /* If we have a solid border, we fill the outline shape with the border + * color and create the inline shape for the background; * otherwise the outline shape is filled with the background - * gradient directly + * directly */ - if (border_width[ST_SIDE_TOP] > 0 || - border_width[ST_SIDE_RIGHT] > 0 || - border_width[ST_SIDE_BOTTOM] > 0 || - border_width[ST_SIDE_LEFT] > 0) + if (border_image == NULL && + (border_width[ST_SIDE_TOP] > 0 || + border_width[ST_SIDE_RIGHT] > 0 || + border_width[ST_SIDE_BOTTOM] > 0 || + border_width[ST_SIDE_LEFT] > 0)) { cairo_set_source_rgba (cr, border_color.red / 255., @@ -532,76 +861,124 @@ st_theme_node_render_gradient (StThemeNode *node) if (radius[ST_CORNER_TOPLEFT] > MAX(border_width[ST_SIDE_TOP], border_width[ST_SIDE_LEFT])) elliptical_arc (cr, - radius[ST_CORNER_TOPLEFT], - radius[ST_CORNER_TOPLEFT], + actor_box.x1 + radius[ST_CORNER_TOPLEFT], + actor_box.y1 + radius[ST_CORNER_TOPLEFT], radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_LEFT], radius[ST_CORNER_TOPLEFT] - border_width[ST_SIDE_TOP], M_PI, 3 * M_PI / 2); else cairo_move_to (cr, - border_width[ST_SIDE_LEFT], - border_width[ST_SIDE_TOP]); + actor_box.x1 + border_width[ST_SIDE_LEFT], + actor_box.y1 + border_width[ST_SIDE_TOP]); cairo_line_to (cr, - node->alloc_width - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]), - border_width[ST_SIDE_TOP]); + actor_box.x2 - MAX(radius[ST_CORNER_TOPRIGHT], border_width[ST_SIDE_RIGHT]), + actor_box.y1 + border_width[ST_SIDE_TOP]); if (radius[ST_CORNER_TOPRIGHT] > MAX(border_width[ST_SIDE_TOP], border_width[ST_SIDE_RIGHT])) elliptical_arc (cr, - node->alloc_width - radius[ST_CORNER_TOPRIGHT], - radius[ST_CORNER_TOPRIGHT], + actor_box.x2 - radius[ST_CORNER_TOPRIGHT], + actor_box.y1 + radius[ST_CORNER_TOPRIGHT], radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_RIGHT], radius[ST_CORNER_TOPRIGHT] - border_width[ST_SIDE_TOP], 3 * M_PI / 2, 2 * M_PI); else cairo_line_to (cr, - node->alloc_width - border_width[ST_SIDE_RIGHT], - border_width[ST_SIDE_TOP]); + actor_box.x2 - border_width[ST_SIDE_RIGHT], + actor_box.y1 + border_width[ST_SIDE_TOP]); cairo_line_to (cr, - node->alloc_width - border_width[ST_SIDE_RIGHT], - node->alloc_height - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM])); + actor_box.x2 - border_width[ST_SIDE_RIGHT], + actor_box.y2 - MAX(radius[ST_CORNER_BOTTOMRIGHT], border_width[ST_SIDE_BOTTOM])); if (radius[ST_CORNER_BOTTOMRIGHT] > MAX(border_width[ST_SIDE_BOTTOM], border_width[ST_SIDE_RIGHT])) elliptical_arc (cr, - node->alloc_width - radius[ST_CORNER_BOTTOMRIGHT], - node->alloc_height - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.x2 - radius[ST_CORNER_BOTTOMRIGHT], + actor_box.y2 - radius[ST_CORNER_BOTTOMRIGHT], radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_RIGHT], radius[ST_CORNER_BOTTOMRIGHT] - border_width[ST_SIDE_BOTTOM], 0, M_PI / 2); else cairo_line_to (cr, - node->alloc_width - border_width[ST_SIDE_RIGHT], - node->alloc_height - border_width[ST_SIDE_BOTTOM]); + actor_box.x2 - border_width[ST_SIDE_RIGHT], + actor_box.y2 - border_width[ST_SIDE_BOTTOM]); cairo_line_to (cr, MAX(radius[ST_CORNER_BOTTOMLEFT], border_width[ST_SIDE_LEFT]), - node->alloc_height - border_width[ST_SIDE_BOTTOM]); + actor_box.y2 - border_width[ST_SIDE_BOTTOM]); if (radius[ST_CORNER_BOTTOMLEFT] > MAX(border_width[ST_SIDE_BOTTOM], border_width[ST_SIDE_LEFT])) elliptical_arc (cr, - radius[ST_CORNER_BOTTOMLEFT], - node->alloc_height - radius[ST_CORNER_BOTTOMLEFT], + actor_box.x1 + radius[ST_CORNER_BOTTOMLEFT], + actor_box.y2 - radius[ST_CORNER_BOTTOMLEFT], radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_LEFT], radius[ST_CORNER_BOTTOMLEFT] - border_width[ST_SIDE_BOTTOM], M_PI / 2, M_PI); else cairo_line_to (cr, - border_width[ST_SIDE_LEFT], - node->alloc_height - border_width[ST_SIDE_BOTTOM]); + actor_box.x1 + border_width[ST_SIDE_LEFT], + actor_box.y2 - border_width[ST_SIDE_BOTTOM]); cairo_close_path (cr); + + interior_path = cairo_copy_path (cr); + + /* clip drawing to the region inside of the borders + */ + cairo_clip (cr); + + /* But fill the pattern as if it started at the edge of outline, + * behind the borders. This is similar to + * background-clip: border-box; semantics. + */ + cairo_append_path (cr, outline_path); } - cairo_set_source (cr, pattern); - cairo_fill (cr); + if (draw_solid_background) + { + cairo_set_source_rgba (cr, + node->background_color.red / 255., + node->background_color.green / 255., + node->background_color.blue / 255., + node->background_color.alpha / 255.); + cairo_fill_preserve (cr); + } - cairo_pattern_destroy (pattern); + if (draw_background_image_shadow) + { + paint_background_image_shadow_to_cairo_context (node, + shadow_spec, + pattern, + cr, + interior_path, + has_visible_outline? outline_path : NULL, + actor_box.x1, + actor_box.y1, + paint_box.x2 - paint_box.x1, + paint_box.y2 - paint_box.y1); + cairo_append_path (cr, outline_path); + } - texture = cogl_texture_new_from_data (node->alloc_width, node->alloc_height, + cairo_translate (cr, actor_box.x1, actor_box.y1); + + if (pattern != NULL) + { + cairo_set_source (cr, pattern); + cairo_fill (cr); + cairo_pattern_destroy (pattern); + } + + if (outline_path != NULL) + cairo_path_destroy (outline_path); + + if (interior_path != NULL) + cairo_path_destroy (interior_path); + + texture = cogl_texture_new_from_data (paint_box.x2 - paint_box.x1, + paint_box.y2 - paint_box.y1, COGL_TEXTURE_NONE, #if G_BYTE_ORDER == G_LITTLE_ENDIAN COGL_PIXEL_FORMAT_BGRA_8888_PRE, @@ -633,12 +1010,16 @@ _st_theme_node_free_drawing_state (StThemeNode *node) cogl_handle_unref (node->background_material); if (node->background_shadow_material != COGL_INVALID_HANDLE) cogl_handle_unref (node->background_shadow_material); - if (node->border_texture != COGL_INVALID_HANDLE) - cogl_handle_unref (node->border_texture); - if (node->border_material != COGL_INVALID_HANDLE) - cogl_handle_unref (node->border_material); - if (node->border_shadow_material != COGL_INVALID_HANDLE) - cogl_handle_unref (node->border_shadow_material); + if (node->border_slices_texture != COGL_INVALID_HANDLE) + cogl_handle_unref (node->border_slices_texture); + if (node->border_slices_material != COGL_INVALID_HANDLE) + cogl_handle_unref (node->border_slices_material); + if (node->prerendered_texture != COGL_INVALID_HANDLE) + cogl_handle_unref (node->prerendered_texture); + if (node->prerendered_material != COGL_INVALID_HANDLE) + cogl_handle_unref (node->prerendered_material); + if (node->box_shadow_material != COGL_INVALID_HANDLE) + cogl_handle_unref (node->box_shadow_material); for (corner_id = 0; corner_id < 4; corner_id++) if (node->corner_material[corner_id] != COGL_INVALID_HANDLE) @@ -655,9 +1036,11 @@ _st_theme_node_init_drawing_state (StThemeNode *node) node->background_texture = COGL_INVALID_HANDLE; node->background_material = COGL_INVALID_HANDLE; node->background_shadow_material = COGL_INVALID_HANDLE; - node->border_shadow_material = COGL_INVALID_HANDLE; - node->border_texture = COGL_INVALID_HANDLE; - node->border_material = COGL_INVALID_HANDLE; + node->box_shadow_material = COGL_INVALID_HANDLE; + node->border_slices_texture = COGL_INVALID_HANDLE; + node->border_slices_material = COGL_INVALID_HANDLE; + node->prerendered_texture = COGL_INVALID_HANDLE; + node->prerendered_material = COGL_INVALID_HANDLE; for (corner_id = 0; corner_id < 4; corner_id++) node->corner_material[corner_id] = COGL_INVALID_HANDLE; @@ -674,7 +1057,10 @@ st_theme_node_render_resources (StThemeNode *node, { StTextureCache *texture_cache; StBorderImage *border_image; - StShadow *shadow_spec; + gboolean has_border; + gboolean has_border_radius; + StShadow *box_shadow_spec; + StShadow *background_image_shadow_spec; const char *background_image; texture_cache = st_texture_cache_get_default (); @@ -691,39 +1077,68 @@ st_theme_node_render_resources (StThemeNode *node, _st_theme_node_ensure_background (node); _st_theme_node_ensure_geometry (node); - shadow_spec = st_theme_node_get_shadow (node); + box_shadow_spec = st_theme_node_get_box_shadow (node); + + if (node->border_width[ST_SIDE_TOP] > 0 || + node->border_width[ST_SIDE_LEFT] > 0 || + node->border_width[ST_SIDE_RIGHT] > 0 || + node->border_width[ST_SIDE_BOTTOM] > 0) + has_border = TRUE; + else + has_border = FALSE; + + if (node->border_radius[ST_CORNER_TOPLEFT] > 0 || + node->border_radius[ST_CORNER_TOPRIGHT] > 0 || + node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 || + node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0) + has_border_radius = TRUE; + else + has_border_radius = FALSE; /* Load referenced images from disk and draw anything we need with cairo now */ - + background_image = st_theme_node_get_background_image (node); border_image = st_theme_node_get_border_image (node); + if (border_image) { const char *filename; filename = st_border_image_get_filename (border_image); - node->border_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename); - } - else if (node->background_gradient_type != ST_GRADIENT_NONE) - { - node->border_texture = st_theme_node_render_gradient (node); + node->border_slices_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, filename); } - if (node->border_texture) - node->border_material = _st_create_texture_material (node->border_texture); + if (node->border_slices_texture) + node->border_slices_material = _st_create_texture_material (node->border_slices_texture); else - node->border_material = COGL_INVALID_HANDLE; + node->border_slices_material = COGL_INVALID_HANDLE; - if (shadow_spec) + /* Use cairo to prerender the node if there is a gradient, or + * background image with borders and/or rounded corners, + * since we can't do those things easily with cogl. + * + * FIXME: if we could figure out ahead of time that a + * background image won't overlap with the node borders, + * then we could use cogl for that case. + */ + if ((node->background_gradient_type != ST_GRADIENT_NONE) + || (background_image && (has_border || has_border_radius))) + node->prerendered_texture = st_theme_node_render_background_with_border (node); + + if (node->prerendered_texture) + node->prerendered_material = _st_create_texture_material (node->prerendered_texture); + else + node->prerendered_material = COGL_INVALID_HANDLE; + + if (box_shadow_spec) { - if (node->border_texture != COGL_INVALID_HANDLE) - node->border_shadow_material = _st_create_shadow_material (shadow_spec, - node->border_texture); - else if (node->background_color.alpha > 0 || - node->border_width[ST_SIDE_TOP] > 0 || - node->border_width[ST_SIDE_LEFT] > 0 || - node->border_width[ST_SIDE_RIGHT] > 0 || - node->border_width[ST_SIDE_BOTTOM] > 0) + if (node->border_slices_texture != COGL_INVALID_HANDLE) + node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, + node->border_slices_texture); + else if (node->prerendered_texture != COGL_INVALID_HANDLE) + node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, + node->prerendered_texture); + else if (node->background_color.alpha > 0 || has_border) { CoglHandle buffer, offscreen; @@ -743,23 +1158,50 @@ st_theme_node_render_resources (StThemeNode *node, cogl_pop_framebuffer (); cogl_handle_unref (offscreen); - node->border_shadow_material = _st_create_shadow_material (shadow_spec, - buffer); + node->box_shadow_material = _st_create_shadow_material (box_shadow_spec, + buffer); } cogl_handle_unref (buffer); } } - background_image = st_theme_node_get_background_image (node); - if (background_image != NULL) + background_image_shadow_spec = st_theme_node_get_background_image_shadow (node); + if (background_image != NULL && !has_border && !has_border_radius) { + CoglHandle texture; + + texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image); + + /* If no background position is specified, then we will automatically scale + * the background to fit within the node allocation. But, if a background + * position is specified, we won't scale the background, and it could + * potentially leak out of bounds. To prevent that, we subtexture from the + * in bounds area when necessary. + */ + if (node->background_position_set && + (cogl_texture_get_width (texture) > width || + cogl_texture_get_height (texture) > height)) + { + CoglHandle subtexture; + + subtexture = cogl_texture_new_from_sub_texture (texture, + 0, 0, + width - node->background_position_x, + height - node->background_position_y); + cogl_handle_unref (texture); + + node->background_texture = subtexture; + } + else + { + node->background_texture = texture; + } - node->background_texture = st_texture_cache_load_file_to_cogl_texture (texture_cache, background_image); node->background_material = _st_create_texture_material (node->background_texture); - if (shadow_spec) + if (background_image_shadow_spec) { - node->background_shadow_material = _st_create_shadow_material (shadow_spec, + node->background_shadow_material = _st_create_shadow_material (background_image_shadow_spec, node->background_texture); } } @@ -1069,8 +1511,8 @@ st_theme_node_paint_sliced_border_image (StThemeNode *node, st_border_image_get_borders (border_image, &border_left, &border_right, &border_top, &border_bottom); - img_width = cogl_texture_get_width (node->border_texture); - img_height = cogl_texture_get_height (node->border_texture); + img_width = cogl_texture_get_width (node->border_slices_texture); + img_height = cogl_texture_get_height (node->border_slices_texture); tx1 = border_left / img_width; tx2 = (img_width - border_right) / img_width; @@ -1085,7 +1527,7 @@ st_theme_node_paint_sliced_border_image (StThemeNode *node, if (ey < 0) ey = border_bottom; /* FIXME ? */ - material = node->border_material; + material = node->border_slices_material; cogl_material_set_color4ub (material, paint_opacity, paint_opacity, paint_opacity, paint_opacity); @@ -1230,41 +1672,58 @@ st_theme_node_paint (StThemeNode *node, * - The combination of border image and a non-zero border radius is * not supported; the background color will be drawn with square * corners. - * - The combination of border image and a background gradient is not - * supported; the background will be drawn as a solid color - * - The background image is drawn above the border color or image, - * not below it. - * - We don't clip the background image to the (rounded) border area. - * - * The first three allow us to always draw with no more than a single - * border_image and a single background image above it. + * - The background image is drawn above the border color, not below it. + * - We clip the background image to the inside edges of the border + * instead of the outside edges of the border (but position the image + * such that it's aligned to the outside edges) */ - if (node->border_shadow_material) - _st_paint_shadow_with_opacity (node->shadow, - node->border_shadow_material, + if (node->box_shadow_material) + _st_paint_shadow_with_opacity (node->box_shadow, + node->box_shadow_material, &allocation, paint_opacity); - if (node->border_material != COGL_INVALID_HANDLE) + if (node->prerendered_material != COGL_INVALID_HANDLE || + node->border_slices_material != COGL_INVALID_HANDLE) { - /* Gradients and border images are mutually exclusive at this time */ - if (node->background_gradient_type != ST_GRADIENT_NONE) - paint_material_with_opacity (node->border_material, &allocation, paint_opacity); - else + if (node->prerendered_material != COGL_INVALID_HANDLE) + { + ClutterActorBox paint_box; + + st_theme_node_get_paint_box (node, &allocation, &paint_box); + + paint_material_with_opacity (node->prerendered_material, + &paint_box, + paint_opacity); + } + + if (node->border_slices_material != COGL_INVALID_HANDLE) st_theme_node_paint_sliced_border_image (node, &allocation, paint_opacity); } else - st_theme_node_paint_borders (node, box, paint_opacity); + { + st_theme_node_paint_borders (node, box, paint_opacity); + } st_theme_node_paint_outline (node, box, paint_opacity); if (node->background_texture != COGL_INVALID_HANDLE) { ClutterActorBox background_box; + gboolean has_visible_outline; + + /* If the background doesn't have a border or opaque background, + * then we let its background image shadows leak out, but other + * wise we clip it. + */ + has_visible_outline = st_theme_node_has_visible_outline (node); get_background_position (node, &allocation, &background_box); + if (has_visible_outline) + cogl_clip_push_rectangle (allocation.x1, allocation.y1, allocation.x2, allocation.y2); + /* CSS based drop shadows * * Drop shadows in ST are modelled after the CSS3 box-shadow property; @@ -1274,16 +1733,20 @@ st_theme_node_paint (StThemeNode *node, * multiple shadows and allow for a more liberal placement of the color * parameter - its interpretation defers significantly in that the shadow's * shape is not determined by the bounding box, but by the CSS background - * image (we could exend this in the future to take other CSS properties - * like boder and background color into account). + * image. The drop shadows are allowed to escape the nodes allocation if + * there is nothing (like a border, or the edge of the background color) + * to logically confine it. */ if (node->background_shadow_material != COGL_INVALID_HANDLE) - _st_paint_shadow_with_opacity (node->shadow, + _st_paint_shadow_with_opacity (node->background_image_shadow, node->background_shadow_material, &background_box, paint_opacity); paint_material_with_opacity (node->background_material, &background_box, paint_opacity); + + if (has_visible_outline) + cogl_clip_pop (); } } @@ -1316,16 +1779,20 @@ st_theme_node_copy_cached_paint_state (StThemeNode *node, if (other->background_shadow_material) node->background_shadow_material = cogl_handle_ref (other->background_shadow_material); - if (other->border_shadow_material) - node->border_shadow_material = cogl_handle_ref (other->border_shadow_material); + if (other->box_shadow_material) + node->box_shadow_material = cogl_handle_ref (other->box_shadow_material); if (other->background_texture) node->background_texture = cogl_handle_ref (other->background_texture); if (other->background_material) node->background_material = cogl_handle_ref (other->background_material); - if (other->border_texture) - node->border_texture = cogl_handle_ref (other->border_texture); - if (other->border_material) - node->border_material = cogl_handle_ref (other->border_material); + if (other->border_slices_texture) + node->border_slices_texture = cogl_handle_ref (other->border_slices_texture); + if (other->border_slices_material) + node->border_slices_material = cogl_handle_ref (other->border_slices_material); + if (other->prerendered_texture) + node->prerendered_texture = cogl_handle_ref (other->prerendered_texture); + if (other->prerendered_material) + node->prerendered_material = cogl_handle_ref (other->prerendered_material); for (corner_id = 0; corner_id < 4; corner_id++) if (other->corner_material[corner_id]) node->corner_material[corner_id] = cogl_handle_ref (other->corner_material[corner_id]); diff --git a/src/st/st-theme-node-private.h b/src/st/st-theme-node-private.h index 7673755e9..f42cf4025 100644 --- a/src/st/st-theme-node-private.h +++ b/src/st/st-theme-node-private.h @@ -65,7 +65,8 @@ struct _StThemeNode { char *background_image; StBorderImage *border_image; - StShadow *shadow; + StShadow *box_shadow; + StShadow *background_image_shadow; StShadow *text_shadow; StIconColors *icon_colors; @@ -86,7 +87,8 @@ struct _StThemeNode { guint background_computed : 1; guint foreground_computed : 1; guint border_image_computed : 1; - guint shadow_computed : 1; + guint box_shadow_computed : 1; + guint background_image_shadow_computed : 1; guint text_shadow_computed : 1; guint link_type : 2; @@ -95,11 +97,13 @@ struct _StThemeNode { float alloc_height; CoglHandle background_shadow_material; - CoglHandle border_shadow_material; + CoglHandle box_shadow_material; CoglHandle background_texture; CoglHandle background_material; - CoglHandle border_texture; - CoglHandle border_material; + CoglHandle border_slices_texture; + CoglHandle border_slices_material; + CoglHandle prerendered_texture; + CoglHandle prerendered_material; CoglHandle corner_material[4]; }; diff --git a/src/st/st-theme-node-transition.c b/src/st/st-theme-node-transition.c index ea1ba1c9e..3da45ae05 100644 --- a/src/st/st-theme-node-transition.c +++ b/src/st/st-theme-node-transition.c @@ -182,6 +182,22 @@ st_theme_node_transition_update (StThemeNodeTransition *transition, static void calculate_offscreen_box (StThemeNodeTransition *transition, const ClutterActorBox *allocation) +{ + ClutterActorBox paint_box; + + st_theme_node_transition_get_paint_box (transition, + allocation, + &paint_box); + transition->priv->offscreen_box.x1 = paint_box.x1 - allocation->x1; + transition->priv->offscreen_box.y1 = paint_box.y1 - allocation->y1; + transition->priv->offscreen_box.x2 = paint_box.x2 - allocation->x1; + transition->priv->offscreen_box.y2 = paint_box.y2 - allocation->y1; +} + +void +st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition, + const ClutterActorBox *allocation, + ClutterActorBox *paint_box) { StThemeNodeTransitionPrivate *priv = transition->priv; ClutterActorBox old_node_box, new_node_box; @@ -194,14 +210,10 @@ calculate_offscreen_box (StThemeNodeTransition *transition, allocation, &new_node_box); - priv->offscreen_box.x1 = MIN (old_node_box.x1, new_node_box.x1) - - allocation->x1; - priv->offscreen_box.y1 = MIN (old_node_box.y1, new_node_box.y1) - - allocation->y1; - priv->offscreen_box.x2 = MAX (old_node_box.x2, new_node_box.x2) - - allocation->x1; - priv->offscreen_box.y2 = MAX (old_node_box.y2, new_node_box.y2) - - allocation->y1; + paint_box->x1 = MIN (old_node_box.x1, new_node_box.x1); + paint_box->y1 = MIN (old_node_box.y1, new_node_box.y1); + paint_box->x2 = MAX (old_node_box.x2, new_node_box.x2); + paint_box->y2 = MAX (old_node_box.y2, new_node_box.y2); } static gboolean diff --git a/src/st/st-theme-node-transition.h b/src/st/st-theme-node-transition.h index 3bcef9beb..d6fd9358a 100644 --- a/src/st/st-theme-node-transition.h +++ b/src/st/st-theme-node-transition.h @@ -65,6 +65,10 @@ void st_theme_node_transition_paint (StThemeNodeTransition *transition, ClutterActorBox *allocation, guint8 paint_opacity); +void st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition, + const ClutterActorBox *allocation, + ClutterActorBox *paint_box); + G_END_DECLS #endif diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c index 4a6a1218d..b57afb654 100644 --- a/src/st/st-theme-node.c +++ b/src/st/st-theme-node.c @@ -122,10 +122,16 @@ st_theme_node_finalize (GObject *object) node->font_desc = NULL; } - if (node->shadow) + if (node->box_shadow) { - st_shadow_unref (node->shadow); - node->shadow = NULL; + st_shadow_unref (node->box_shadow); + node->box_shadow = NULL; + } + + if (node->background_image_shadow) + { + st_shadow_unref (node->background_image_shadow); + node->background_image_shadow = NULL; } if (node->text_shadow) @@ -2606,16 +2612,17 @@ st_theme_node_get_vertical_padding (StThemeNode *node) return padding; } -static void -parse_shadow_property (StThemeNode *node, - CRDeclaration *decl, - ClutterColor *color, - gdouble *xoffset, - gdouble *yoffset, - gdouble *blur, - gdouble *spread) +static GetFromTermResult +parse_shadow_property (StThemeNode *node, + CRDeclaration *decl, + ClutterColor *color, + gdouble *xoffset, + gdouble *yoffset, + gdouble *blur, + gdouble *spread) { /* Set value for width/color/blur in any order */ + GetFromTermResult result; CRTerm *term; int n_offsets = 0; @@ -2628,8 +2635,6 @@ parse_shadow_property (StThemeNode *node, for (term = decl->value; term; term = term->next) { - GetFromTermResult result; - if (term->type == TERM_NUMBER) { gdouble value; @@ -2637,7 +2642,16 @@ parse_shadow_property (StThemeNode *node, multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.; result = get_length_from_term (node, term, FALSE, &value); - if (result != VALUE_NOT_FOUND) + + if (result == VALUE_INHERIT) + { + /* we only allow inherit on the line by itself */ + if (n_offsets > 0) + return VALUE_NOT_FOUND; + else + return VALUE_INHERIT; + } + else if (result == VALUE_FOUND) { switch (n_offsets++) { @@ -2665,57 +2679,200 @@ parse_shadow_property (StThemeNode *node, } result = get_color_from_term (node, term, color); - if (result != VALUE_NOT_FOUND) + + if (result == VALUE_INHERIT) + { + if (n_offsets > 0) + return VALUE_NOT_FOUND; + else + return VALUE_INHERIT; + } + else if (result == VALUE_FOUND) { continue; } } + + /* The only required terms are the x and y offsets + */ + if (n_offsets >= 2) + return VALUE_FOUND; + else + return VALUE_NOT_FOUND; } /** - * st_theme_node_get_shadow: + * st_theme_node_lookup_shadow: * @node: a #StThemeNode + * @property_name: The name of the shadow property + * @inherit: if %TRUE, if a value is not found for the property on the + * node, then it will be looked up on the parent node, and then on the + * parent's parent, and so forth. Note that if the property has a + * value of 'inherit' it will be inherited even if %FALSE is passed + * in for @inherit; this only affects the default behavior for inheritance. + * @shadow: (out): location to store the shadow * - * Gets the value for the -st-shadow style property + * If the property is not found, the value in the shadow variable will not + * be changed. * - * Return value: (transfer none): the node's shadow, or %NULL - * if node has no shadow + * Generically looks up a property containing a set of shadow values. When + * specific getters (like st_theme_node_get_box_shadow ()) exist, they + * should be used instead. They are cached, so more efficient, and have + * handling for shortcut properties and other details of CSS. + * + * See also st_theme_node_get_shadow(), which provides a simpler API. + * + * Return value: %TRUE if the property was found in the properties for this + * theme node (or in the properties of parent nodes when inheriting.) */ -StShadow * -st_theme_node_get_shadow (StThemeNode *node) +gboolean +st_theme_node_lookup_shadow (StThemeNode *node, + const char *property_name, + gboolean inherit, + StShadow **shadow) { + ClutterColor color = { 0., }; + gdouble xoffset = 0.; + gdouble yoffset = 0.; + gdouble blur = 0.; + gdouble spread = 0.; + int i; - if (node->shadow_computed) - return node->shadow; - - node->shadow = NULL; - node->shadow_computed = TRUE; - ensure_properties (node); for (i = node->n_properties - 1; i >= 0; i--) { CRDeclaration *decl = node->properties[i]; - if (strcmp (decl->property->stryng->str, "-st-shadow") == 0) + if (strcmp (decl->property->stryng->str, property_name) == 0) { - ClutterColor color; - gdouble xoffset; - gdouble yoffset; - gdouble blur; - gdouble spread; - - parse_shadow_property (node, decl, - &color, &xoffset, &yoffset, &blur, &spread); - - node->shadow = st_shadow_new (&color, - xoffset, yoffset, - blur, spread); - - return node->shadow; + GetFromTermResult result = parse_shadow_property (node, + decl, + &color, + &xoffset, + &yoffset, + &blur, + &spread); + if (result == VALUE_FOUND) + { + *shadow = st_shadow_new (&color, xoffset, yoffset, blur, spread); + return TRUE; + } + else if (result == VALUE_INHERIT) + { + if (node->parent_node) + return st_theme_node_lookup_shadow (node->parent_node, + property_name, + inherit, + shadow); + else + break; + } } } + + if (inherit && node->parent_node) + return st_theme_node_lookup_shadow (node->parent_node, + property_name, + inherit, + shadow); + + return FALSE; +} + +/** + * st_theme_node_get_shadow: + * @node: a #StThemeNode + * @property_name: The name of the shadow property + * + * Generically looks up a property containing a set of shadow values. When + * specific getters (like st_theme_node_get_box_shadow()) exist, they + * should be used instead. They are cached, so more efficient, and have + * handling for shortcut properties and other details of CSS. + * + * Like st_theme_get_length(), this does not print a warning if the property is + * not found; it just returns %NULL + * + * See also st_theme_node_lookup_shadow (), which provides more options. + * + * Return value: (transfer full): the shadow, or %NULL if the property was not found. + */ +StShadow * +st_theme_node_get_shadow (StThemeNode *node, + const char *property_name) +{ + StShadow *shadow; + + if (st_theme_node_lookup_shadow (node, property_name, FALSE, &shadow)) + return shadow; + else + return NULL; +} + +/** + * st_theme_node_get_box_shadow: + * @node: a #StThemeNode + * + * Gets the value for the box-shadow style property + * + * Return value: (transfer none): the node's shadow, or %NULL + * if node has no shadow + */ +StShadow * +st_theme_node_get_box_shadow (StThemeNode *node) +{ + StShadow *shadow; + + if (node->box_shadow_computed) + return node->box_shadow; + + node->box_shadow = NULL; + node->box_shadow_computed = TRUE; + + if (st_theme_node_lookup_shadow (node, + "box-shadow", + FALSE, + &shadow)) + { + node->box_shadow = shadow; + + return node->box_shadow; + } + + return NULL; +} + +/** + * st_theme_node_get_background_image_shadow: + * @node: a #StThemeNode + * + * Gets the value for the -st-background-image-shadow style property + * + * Return value: (transfer none): the node's background image shadow, or %NULL + * if node has no such shadow + */ +StShadow * +st_theme_node_get_background_image_shadow (StThemeNode *node) +{ + StShadow *shadow; + + if (node->background_image_shadow_computed) + return node->background_image_shadow; + + node->background_image_shadow = NULL; + node->background_image_shadow_computed = TRUE; + + if (st_theme_node_lookup_shadow (node, + "-st-background-image-shadow", + FALSE, + &shadow)) + { + node->background_image_shadow = shadow; + + return node->background_image_shadow; + } + return NULL; } @@ -2732,43 +2889,25 @@ StShadow * st_theme_node_get_text_shadow (StThemeNode *node) { StShadow *result = NULL; - int i; if (node->text_shadow_computed) return node->text_shadow; ensure_properties (node); - for (i = node->n_properties - 1; i >= 0; i--) + if (!st_theme_node_lookup_shadow (node, + "text-shadow", + FALSE, + &result)) { - CRDeclaration *decl = node->properties[i]; - - if (strcmp (decl->property->stryng->str, "text-shadow") == 0) + if (node->parent_node) { - ClutterColor color; - gdouble xoffset; - gdouble yoffset; - gdouble blur; - gdouble spread; - - parse_shadow_property (node, decl, - &color, &xoffset, &yoffset, &blur, &spread); - - result = st_shadow_new (&color, - xoffset, yoffset, - blur, spread); - - break; + result = st_theme_node_get_text_shadow (node->parent_node); + if (result) + st_shadow_ref (result); } } - if (!result && node->parent_node) - { - result = st_theme_node_get_text_shadow (node->parent_node); - if (result) - st_shadow_ref (result); - } - node->text_shadow = result; node->text_shadow_computed = TRUE; @@ -3102,7 +3241,8 @@ st_theme_node_get_paint_box (StThemeNode *node, const ClutterActorBox *actor_box, ClutterActorBox *paint_box) { - StShadow *shadow; + StShadow *box_shadow; + StShadow *background_image_shadow; ClutterActorBox shadow_box; int outline_width; @@ -3110,26 +3250,41 @@ st_theme_node_get_paint_box (StThemeNode *node, g_return_if_fail (actor_box != NULL); g_return_if_fail (paint_box != NULL); - shadow = st_theme_node_get_shadow (node); + box_shadow = st_theme_node_get_box_shadow (node); + background_image_shadow = st_theme_node_get_background_image_shadow (node); outline_width = st_theme_node_get_outline_width (node); - if (!shadow && !outline_width) + + *paint_box = *actor_box; + + if (!box_shadow && !background_image_shadow && !outline_width) + return; + + paint_box->x1 -= outline_width; + paint_box->x2 += outline_width; + paint_box->y1 -= outline_width; + paint_box->y2 += outline_width; + + if (box_shadow) { - *paint_box = *actor_box; - return; + st_shadow_get_box (box_shadow, actor_box, &shadow_box); + + paint_box->x1 = MIN (paint_box->x1, shadow_box.x1); + paint_box->x2 = MAX (paint_box->x2, shadow_box.x2); + paint_box->y1 = MIN (paint_box->y1, shadow_box.y1); + paint_box->y2 = MAX (paint_box->y2, shadow_box.y2); } - if (shadow) - st_shadow_get_box (shadow, actor_box, &shadow_box); - else - shadow_box = *actor_box; + if (background_image_shadow) + { + st_shadow_get_box (background_image_shadow, actor_box, &shadow_box); - paint_box->x1 = MIN (actor_box->x1 - outline_width, shadow_box.x1); - paint_box->x2 = MAX (actor_box->x2 + outline_width, shadow_box.x2); - paint_box->y1 = MIN (actor_box->y1 - outline_width, shadow_box.y1); - paint_box->y2 = MAX (actor_box->y2 + outline_width, shadow_box.y2); + paint_box->x1 = MIN (paint_box->x1, shadow_box.x1); + paint_box->x2 = MAX (paint_box->x2, shadow_box.x2); + paint_box->y1 = MIN (paint_box->y1, shadow_box.y1); + paint_box->y2 = MAX (paint_box->y2, shadow_box.y2); + } } - /** * st_theme_node_geometry_equal: * @node: a #StThemeNode @@ -3240,8 +3395,17 @@ st_theme_node_paint_equal (StThemeNode *node, if (border_image != NULL && !st_border_image_equal (border_image, other_border_image)) return FALSE; - shadow = st_theme_node_get_shadow (node); - other_shadow = st_theme_node_get_shadow (other); + shadow = st_theme_node_get_box_shadow (node); + other_shadow = st_theme_node_get_box_shadow (other); + + if ((shadow == NULL) != (other_shadow == NULL)) + return FALSE; + + if (shadow != NULL && !st_shadow_equal (shadow, other_shadow)) + return FALSE; + + shadow = st_theme_node_get_background_image_shadow (node); + other_shadow = st_theme_node_get_background_image_shadow (other); if ((shadow == NULL) != (other_shadow == NULL)) return FALSE; diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h index 13e0bf63c..ad9e4e097 100644 --- a/src/st/st-theme-node.h +++ b/src/st/st-theme-node.h @@ -133,6 +133,10 @@ gboolean st_theme_node_lookup_length (StThemeNode *node, const char *property_name, gboolean inherit, gdouble *length); +gboolean st_theme_node_lookup_shadow (StThemeNode *node, + const char *property_name, + gboolean inherit, + StShadow **shadow); /* Easier-to-use variants of the above, for application-level use */ void st_theme_node_get_color (StThemeNode *node, @@ -142,6 +146,8 @@ gdouble st_theme_node_get_double (StThemeNode *node, const char *property_name); gdouble st_theme_node_get_length (StThemeNode *node, const char *property_name); +StShadow *st_theme_node_get_shadow (StThemeNode *node, + const char *property_name); /* Specific getters for particular properties: cached */ @@ -195,9 +201,11 @@ StTextAlign st_theme_node_get_text_align (StThemeNode *node); const PangoFontDescription *st_theme_node_get_font (StThemeNode *node); StBorderImage *st_theme_node_get_border_image (StThemeNode *node); -StShadow *st_theme_node_get_shadow (StThemeNode *node); +StShadow *st_theme_node_get_box_shadow (StThemeNode *node); StShadow *st_theme_node_get_text_shadow (StThemeNode *node); +StShadow *st_theme_node_get_background_image_shadow (StThemeNode *node); + StIconColors *st_theme_node_get_icon_colors (StThemeNode *node); /* Helpers for get_preferred_width()/get_preferred_height() ClutterActor vfuncs */ diff --git a/src/st/st-widget.c b/src/st/st-widget.c index 8ebe1dd27..09b282413 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -357,13 +357,10 @@ st_widget_allocate (ClutterActor *actor, { StWidget *self = ST_WIDGET (actor); StWidgetPrivate *priv = self->priv; - StThemeNode *theme_node; ClutterActorClass *klass; ClutterGeometry area; ClutterVertex in_v, out_v; - theme_node = st_widget_get_theme_node (self); - klass = CLUTTER_ACTOR_CLASS (st_widget_parent_class); klass->allocate (actor, box, flags); @@ -686,15 +683,23 @@ st_widget_get_paint_volume (ClutterActor *self, ClutterPaintVolume *volume) { ClutterActorBox paint_box, alloc_box; StThemeNode *theme_node; + StWidgetPrivate *priv; ClutterVertex origin; /* Setting the paint volume does not make sense when we don't have any allocation */ if (!clutter_actor_has_allocation (self)) return FALSE; + priv = ST_WIDGET(self)->priv; + theme_node = st_widget_get_theme_node (ST_WIDGET(self)); clutter_actor_get_allocation_box (self, &alloc_box); - st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box); + + if (priv->transition_animation) + st_theme_node_transition_get_paint_box (priv->transition_animation, + &alloc_box, &paint_box); + else + st_theme_node_get_paint_box (theme_node, &alloc_box, &paint_box); origin.x = paint_box.x1 - alloc_box.x1; origin.y = paint_box.y1 - alloc_box.y1; diff --git a/tests/Makefile.am b/tests/Makefile.am index 9fb38a294..5958ffb11 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,6 +15,7 @@ TEST_JS = \ interactive/scroll-view-sizing.js \ interactive/table.js \ testcommon/border-image.png \ + testcommon/face-plain.png \ testcommon/ui.js \ unit/format.js EXTRA_DIST += $(TEST_JS) diff --git a/tests/interactive/borders.js b/tests/interactive/borders.js index 7c5cda13a..ba8d1e0ac 100644 --- a/tests/interactive/borders.js +++ b/tests/interactive/borders.js @@ -71,6 +71,65 @@ box.add(new St.Label({ text: "Border Image", style_class: "border-image", style: "padding: 10px;" })); +box.add(new St.Label({ text: "Border Image with Gradient", + style_class: 'border-image-with-background-gradient', + style: "padding: 10px;" + + 'background-gradient-direction: vertical;' })); + +box.add(new St.Label({ text: "Rounded, framed, shadowed gradients" })); + +let framedGradients = new St.BoxLayout({ vertical: false, + style: 'padding: 10px; spacing: 12px;' }); +box.add(framedGradients); + +function addGradientCase(direction, borderWidth, borderRadius, extra) { + let gradientBox = new St.BoxLayout({ style_class: 'background-gradient', + style: 'border: ' + borderWidth + 'px solid #8b8b8b;' + + 'border-radius: ' + borderRadius + 'px;' + + 'background-gradient-direction: ' + direction + ';' + + 'width: 32px;' + + 'height: 32px;' + + extra }); + framedGradients.add(gradientBox, { x_fill: false, y_fill: false } ); +} + +addGradientCase ('horizontal', 0, 5, 'box-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); +addGradientCase ('horizontal', 2, 5, 'box-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); +addGradientCase ('horizontal', 5, 2, 'box-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); +addGradientCase ('horizontal', 5, 20, 'box-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); +addGradientCase ('vertical', 0, 5, 'box-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 2, 5, 'box-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 5, 2, 'box-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); +addGradientCase ('vertical', 5, 20, 'box-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); + +box.add(new St.Label({ text: "Rounded, framed, shadowed images" })); + +let framedImages = new St.BoxLayout({ vertical: false, + style: 'padding: 10px; spacing: 6px;' }); +box.add(framedImages); + +function addBackgroundImageCase(borderWidth, borderRadius, width, height, extra) { + let imageBox = new St.BoxLayout({ style_class: 'background-image', + style: 'border: ' + borderWidth + 'px solid #8b8b8b;' + + 'border-radius: ' + borderRadius + 'px;' + + 'width: ' + width + 'px;' + + 'height: ' + height + 'px;' + + extra }); + framedImages.add(imageBox, { x_fill: false, y_fill: false } ); +} + +addBackgroundImageCase (0, 0, 32, 32, 'background-position: 2px 5px'); +addBackgroundImageCase (0, 0, 16, 16, '-st-background-image-shadow: 1px 1px 4px 0px rgba(0,0,0,0.5); background-color: rgba(0,0,0,0)'); +addBackgroundImageCase (0, 5, 32, 32, '-st-background-image-shadow: 0px 0px 0px 0px rgba(0,0,0,0.5);'); +addBackgroundImageCase (2, 5, 32, 32, '-st-background-image-shadow: 0px 2px 0px 0px rgba(0,255,0,0.5);'); +addBackgroundImageCase (5, 2, 32, 32, '-st-background-image-shadow: 2px 0px 0px 0px rgba(0,0,255,0.5);'); +addBackgroundImageCase (5, 20, 32, 32, '-st-background-image-shadow: 0px 0px 4px 0px rgba(255,0,0,0.5);'); +addBackgroundImageCase (0, 5, 48, 48, '-st-background-image-shadow: 0px 0px 0px 4px rgba(0,0,0,0.5);'); +addBackgroundImageCase (5, 5, 48, 48, '-st-background-image-shadow: 0px 0px 4px 4px rgba(0,0,0,0.5);'); +addBackgroundImageCase (0, 5, 64, 64, '-st-background-image-shadow: -2px -2px 6px 0px rgba(0,0,0,0.5);'); +addBackgroundImageCase (5, 5, 64, 64, '-st-background-image-shadow: -2px -2px 0px 6px rgba(0,0,0,0.5);'); +addBackgroundImageCase (0, 5, 32, 32, 'background-position: 2px 5px'); + stage.show(); Clutter.main(); stage.destroy(); diff --git a/tests/interactive/scroll-view-sizing.js b/tests/interactive/scroll-view-sizing.js index c7c8c9541..4ea5b8a58 100644 --- a/tests/interactive/scroll-view-sizing.js +++ b/tests/interactive/scroll-view-sizing.js @@ -319,17 +319,17 @@ function togglePolicy(button) { hpolicy.connect('clicked', function() { togglePolicy(hpolicy); }); vpolicy.connect('clicked', function() { togglePolicy(vpolicy); }); -let shadowsBox = new St.BoxLayout({ vertical: false }); -mainBox.add(shadowsBox); +let fadeBox = new St.BoxLayout({ vertical: false }); +mainBox.add(fadeBox); spacer = new St.Bin(); -shadowsBox.add(spacer, { expand: true }); +fadeBox.add(spacer, { expand: true }); -shadowsBox.add(new St.Label({ text: 'Vertical Shadows: '})); -let vshadows = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;' }); -shadowsBox.add(vshadows); +fadeBox.add(new St.Label({ text: 'Vertical Fade: '})); +let vfade = new St.Button({ label: 'No', style: 'text-decoration: underline; color: #4444ff;' }); +fadeBox.add(vfade); -function toggleShadows(button) { +function toggleFade(button) { switch(button.label) { case 'No': button.label = 'Yes'; @@ -338,10 +338,10 @@ function toggleShadows(button) { button.label = 'No'; break; } - scrollView.set_vshadows(vshadows.label == 'Yes'); + scrollView.set_vfade(vfade.label == 'Yes'); } -vshadows.connect('clicked', function() { toggleShadows(vshadows); }); +vfade.connect('clicked', function() { toggleFade(vfade); }); stage.show(); Clutter.main(); diff --git a/tests/testcommon/face-plain.png b/tests/testcommon/face-plain.png new file mode 100644 index 000000000..962d70f1b Binary files /dev/null and b/tests/testcommon/face-plain.png differ diff --git a/tests/testcommon/test.css b/tests/testcommon/test.css index d1005eeb3..34611118b 100644 --- a/tests/testcommon/test.css +++ b/tests/testcommon/test.css @@ -37,6 +37,23 @@ stage { border-image: url('border-image.png') 16; } +.background-gradient { + background-gradient-start: #88ff88; + background-gradient-end: #8888ff; +} + +.border-image-with-background-gradient { + border: 15px black solid; + border-image: url('border-image.png') 16; + background-gradient-start: #88ff88; + background-gradient-end: #8888ff; +} + +.background-image { + background-image: url('face-plain.png'); + background-color: white; +} + .push-button { background: #eeddbb; border: 1px solid black;